diff --git a/OliverBooth/Data/Blog/IBlogAuthor.cs b/OliverBooth.Common/Data/Blog/IBlogAuthor.cs similarity index 95% rename from OliverBooth/Data/Blog/IBlogAuthor.cs rename to OliverBooth.Common/Data/Blog/IBlogAuthor.cs index 3f1c3e1..279da26 100644 --- a/OliverBooth/Data/Blog/IBlogAuthor.cs +++ b/OliverBooth.Common/Data/Blog/IBlogAuthor.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Blog; +namespace OliverBooth.Common.Data.Blog; /// /// Represents the author of a blog post. diff --git a/OliverBooth/Data/Blog/IBlogPost.cs b/OliverBooth.Common/Data/Blog/IBlogPost.cs similarity index 98% rename from OliverBooth/Data/Blog/IBlogPost.cs rename to OliverBooth.Common/Data/Blog/IBlogPost.cs index bc1a2e5..093220c 100644 --- a/OliverBooth/Data/Blog/IBlogPost.cs +++ b/OliverBooth.Common/Data/Blog/IBlogPost.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Blog; +namespace OliverBooth.Common.Data.Blog; /// /// Represents a blog post. diff --git a/OliverBooth/Data/Blog/ILegacyComment.cs b/OliverBooth.Common/Data/Blog/ILegacyComment.cs similarity index 97% rename from OliverBooth/Data/Blog/ILegacyComment.cs rename to OliverBooth.Common/Data/Blog/ILegacyComment.cs index 2f43dc2..2a5cc99 100644 --- a/OliverBooth/Data/Blog/ILegacyComment.cs +++ b/OliverBooth.Common/Data/Blog/ILegacyComment.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Blog; +namespace OliverBooth.Common.Data.Blog; /// /// Represents a comment that was posted on a legacy comment framework. diff --git a/OliverBooth/Data/Blog/IUser.cs b/OliverBooth.Common/Data/Blog/IUser.cs similarity index 97% rename from OliverBooth/Data/Blog/IUser.cs rename to OliverBooth.Common/Data/Blog/IUser.cs index 912db14..80af1b8 100644 --- a/OliverBooth/Data/Blog/IUser.cs +++ b/OliverBooth.Common/Data/Blog/IUser.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Blog; +namespace OliverBooth.Common.Data.Blog; /// /// Represents a user which can log in to the blog. diff --git a/OliverBooth/Data/Mastodon/AttachmentType.cs b/OliverBooth.Common/Data/Mastodon/AttachmentType.cs similarity index 66% rename from OliverBooth/Data/Mastodon/AttachmentType.cs rename to OliverBooth.Common/Data/Mastodon/AttachmentType.cs index 6d0b5e1..701db4e 100644 --- a/OliverBooth/Data/Mastodon/AttachmentType.cs +++ b/OliverBooth.Common/Data/Mastodon/AttachmentType.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Mastodon; +namespace OliverBooth.Common.Data.Mastodon; public enum AttachmentType { diff --git a/OliverBooth.Common/Data/Mastodon/IMastodonStatus.cs b/OliverBooth.Common/Data/Mastodon/IMastodonStatus.cs new file mode 100644 index 0000000..f7bdc95 --- /dev/null +++ b/OliverBooth.Common/Data/Mastodon/IMastodonStatus.cs @@ -0,0 +1,31 @@ +namespace OliverBooth.Common.Data.Mastodon; + +/// +/// Represents a status on Mastodon. +/// +public interface IMastodonStatus +{ + /// + /// Gets the content of the status. + /// + /// The content. + string Content { get; } + + /// + /// Gets the date and time at which this status was posted. + /// + /// The post timestamp. + DateTimeOffset CreatedAt { get; } + + /// + /// Gets the media attachments for this status. + /// + /// The media attachments. + IReadOnlyList MediaAttachments { get; } + + /// + /// Gets the original URI of the status. + /// + /// The original URI. + Uri OriginalUri { get; } +} diff --git a/OliverBooth/Data/Mastodon/MediaAttachment.cs b/OliverBooth.Common/Data/Mastodon/MediaAttachment.cs similarity index 92% rename from OliverBooth/Data/Mastodon/MediaAttachment.cs rename to OliverBooth.Common/Data/Mastodon/MediaAttachment.cs index ddc3307..117be81 100644 --- a/OliverBooth/Data/Mastodon/MediaAttachment.cs +++ b/OliverBooth.Common/Data/Mastodon/MediaAttachment.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Mastodon; +namespace OliverBooth.Common.Data.Mastodon; public sealed class MediaAttachment { diff --git a/OliverBooth/Data/Visibility.cs b/OliverBooth.Common/Data/Visibility.cs similarity index 94% rename from OliverBooth/Data/Visibility.cs rename to OliverBooth.Common/Data/Visibility.cs index 17b3e87..5bd0e92 100644 --- a/OliverBooth/Data/Visibility.cs +++ b/OliverBooth.Common/Data/Visibility.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data; +namespace OliverBooth.Common.Data; /// /// An enumeration of the possible visibilities of a blog post. diff --git a/OliverBooth/Data/Web/BookState.cs b/OliverBooth.Common/Data/Web/BookState.cs similarity index 90% rename from OliverBooth/Data/Web/BookState.cs rename to OliverBooth.Common/Data/Web/BookState.cs index 637415a..29b8312 100644 --- a/OliverBooth/Data/Web/BookState.cs +++ b/OliverBooth.Common/Data/Web/BookState.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents the state of a book. diff --git a/OliverBooth/Data/Web/IBlacklistEntry.cs b/OliverBooth.Common/Data/Web/IBlacklistEntry.cs similarity index 93% rename from OliverBooth/Data/Web/IBlacklistEntry.cs rename to OliverBooth.Common/Data/Web/IBlacklistEntry.cs index 00516fb..6c55acc 100644 --- a/OliverBooth/Data/Web/IBlacklistEntry.cs +++ b/OliverBooth.Common/Data/Web/IBlacklistEntry.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents an entry in the blacklist. diff --git a/OliverBooth/Data/Web/IBook.cs b/OliverBooth.Common/Data/Web/IBook.cs similarity index 95% rename from OliverBooth/Data/Web/IBook.cs rename to OliverBooth.Common/Data/Web/IBook.cs index 5c71d85..77f5856 100644 --- a/OliverBooth/Data/Web/IBook.cs +++ b/OliverBooth.Common/Data/Web/IBook.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents a book. diff --git a/OliverBooth/Data/Web/ICodeSnippet.cs b/OliverBooth.Common/Data/Web/ICodeSnippet.cs similarity index 93% rename from OliverBooth/Data/Web/ICodeSnippet.cs rename to OliverBooth.Common/Data/Web/ICodeSnippet.cs index ba1d324..29ec491 100644 --- a/OliverBooth/Data/Web/ICodeSnippet.cs +++ b/OliverBooth.Common/Data/Web/ICodeSnippet.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents a code snippet. diff --git a/OliverBooth/Data/Web/IProgrammingLanguage.cs b/OliverBooth.Common/Data/Web/IProgrammingLanguage.cs similarity index 92% rename from OliverBooth/Data/Web/IProgrammingLanguage.cs rename to OliverBooth.Common/Data/Web/IProgrammingLanguage.cs index 039f31a..464b943 100644 --- a/OliverBooth/Data/Web/IProgrammingLanguage.cs +++ b/OliverBooth.Common/Data/Web/IProgrammingLanguage.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents a programming language. diff --git a/OliverBooth/Data/Web/IProject.cs b/OliverBooth.Common/Data/Web/IProject.cs similarity index 98% rename from OliverBooth/Data/Web/IProject.cs rename to OliverBooth.Common/Data/Web/IProject.cs index f2cc0d8..0b41719 100644 --- a/OliverBooth/Data/Web/IProject.cs +++ b/OliverBooth.Common/Data/Web/IProject.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents a project. diff --git a/OliverBooth/Data/Web/ITemplate.cs b/OliverBooth.Common/Data/Web/ITemplate.cs similarity index 92% rename from OliverBooth/Data/Web/ITemplate.cs rename to OliverBooth.Common/Data/Web/ITemplate.cs index 50ad893..b3bd1e5 100644 --- a/OliverBooth/Data/Web/ITemplate.cs +++ b/OliverBooth.Common/Data/Web/ITemplate.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents a template. diff --git a/OliverBooth/Data/Web/ITutorialArticle.cs b/OliverBooth.Common/Data/Web/ITutorialArticle.cs similarity index 98% rename from OliverBooth/Data/Web/ITutorialArticle.cs rename to OliverBooth.Common/Data/Web/ITutorialArticle.cs index a53b289..7d27e63 100644 --- a/OliverBooth/Data/Web/ITutorialArticle.cs +++ b/OliverBooth.Common/Data/Web/ITutorialArticle.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents a tutorial article. diff --git a/OliverBooth/Data/Web/ITutorialFolder.cs b/OliverBooth.Common/Data/Web/ITutorialFolder.cs similarity index 96% rename from OliverBooth/Data/Web/ITutorialFolder.cs rename to OliverBooth.Common/Data/Web/ITutorialFolder.cs index 65a3cd2..0544d85 100644 --- a/OliverBooth/Data/Web/ITutorialFolder.cs +++ b/OliverBooth.Common/Data/Web/ITutorialFolder.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents a folder for tutorial articles. diff --git a/OliverBooth/Data/Web/ProjectStatus.cs b/OliverBooth.Common/Data/Web/ProjectStatus.cs similarity index 92% rename from OliverBooth/Data/Web/ProjectStatus.cs rename to OliverBooth.Common/Data/Web/ProjectStatus.cs index a3cbf49..a7e4cb1 100644 --- a/OliverBooth/Data/Web/ProjectStatus.cs +++ b/OliverBooth.Common/Data/Web/ProjectStatus.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace OliverBooth.Data.Web; +namespace OliverBooth.Common.Data.Web; /// /// Represents the status of a project. diff --git a/OliverBooth.Common/OliverBooth.Common.csproj b/OliverBooth.Common/OliverBooth.Common.csproj new file mode 100644 index 0000000..5f634e5 --- /dev/null +++ b/OliverBooth.Common/OliverBooth.Common.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/OliverBooth/Services/IBlogPostService.cs b/OliverBooth.Common/Services/IBlogPostService.cs similarity index 88% rename from OliverBooth/Services/IBlogPostService.cs rename to OliverBooth.Common/Services/IBlogPostService.cs index 5947f19..5517539 100644 --- a/OliverBooth/Services/IBlogPostService.cs +++ b/OliverBooth.Common/Services/IBlogPostService.cs @@ -1,7 +1,8 @@ using System.Diagnostics.CodeAnalysis; -using OliverBooth.Data.Blog; +using OliverBooth.Common.Data; +using OliverBooth.Common.Data.Blog; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; /// /// Represents a service for managing blog posts. @@ -22,8 +23,9 @@ public interface IBlogPostService /// /// Returns the total number of blog posts. /// + /// The post visibility filter. /// The total number of blog posts. - int GetBlogPostCount(); + int GetBlogPostCount(Visibility visibility = Visibility.None); /// /// Returns a collection of blog posts from the specified page, optionally limiting the number of posts @@ -62,6 +64,15 @@ public interface IBlogPostService /// The next blog post from the specified blog post. IBlogPost? GetNextPost(IBlogPost blogPost); + /// + /// Returns the number of pages needed to render all blog posts, using the specified as an + /// indicator of how many posts are allowed per page. + /// + /// The page size. Defaults to 10. + /// The post visibility filter. + /// The page count. + int GetPageCount(int pageSize = 10, Visibility visibility = Visibility.None); + /// /// Returns the previous blog post from the specified blog post. /// diff --git a/OliverBooth/Services/IBlogUserService.cs b/OliverBooth.Common/Services/IBlogUserService.cs similarity index 90% rename from OliverBooth/Services/IBlogUserService.cs rename to OliverBooth.Common/Services/IBlogUserService.cs index 0a117ae..83577e3 100644 --- a/OliverBooth/Services/IBlogUserService.cs +++ b/OliverBooth.Common/Services/IBlogUserService.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using OliverBooth.Data.Blog; +using OliverBooth.Common.Data.Blog; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; /// /// Represents a service for managing users. diff --git a/OliverBooth/Services/ICodeSnippetService.cs b/OliverBooth.Common/Services/ICodeSnippetService.cs similarity index 95% rename from OliverBooth/Services/ICodeSnippetService.cs rename to OliverBooth.Common/Services/ICodeSnippetService.cs index b81cb88..d065ce0 100644 --- a/OliverBooth/Services/ICodeSnippetService.cs +++ b/OliverBooth.Common/Services/ICodeSnippetService.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using OliverBooth.Data.Web; +using OliverBooth.Common.Data.Web; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; /// /// Represents a service which can fetch multi-language code snippets. diff --git a/OliverBooth/Services/IContactService.cs b/OliverBooth.Common/Services/IContactService.cs similarity index 77% rename from OliverBooth/Services/IContactService.cs rename to OliverBooth.Common/Services/IContactService.cs index 800a3e7..38b1e9c 100644 --- a/OliverBooth/Services/IContactService.cs +++ b/OliverBooth.Common/Services/IContactService.cs @@ -1,6 +1,6 @@ -using OliverBooth.Data.Web; +using OliverBooth.Common.Data.Web; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; /// /// Represents a service for managing contact information. diff --git a/OliverBooth/Services/IMastodonService.cs b/OliverBooth.Common/Services/IMastodonService.cs similarity index 59% rename from OliverBooth/Services/IMastodonService.cs rename to OliverBooth.Common/Services/IMastodonService.cs index 2126d46..dc38a51 100644 --- a/OliverBooth/Services/IMastodonService.cs +++ b/OliverBooth.Common/Services/IMastodonService.cs @@ -1,6 +1,6 @@ -using OliverBooth.Data.Mastodon; +using OliverBooth.Common.Data.Mastodon; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; public interface IMastodonService { @@ -8,5 +8,5 @@ public interface IMastodonService /// Gets the latest status posted to Mastodon. /// /// The latest status. - MastodonStatus GetLatestStatus(); + IMastodonStatus GetLatestStatus(); } \ No newline at end of file diff --git a/OliverBooth.Common/Services/IProgrammingLanguageService.cs b/OliverBooth.Common/Services/IProgrammingLanguageService.cs new file mode 100644 index 0000000..dc0acd0 --- /dev/null +++ b/OliverBooth.Common/Services/IProgrammingLanguageService.cs @@ -0,0 +1,14 @@ +namespace OliverBooth.Common.Services; + +/// +/// Represents a service which can perform programming language lookup. +/// +public interface IProgrammingLanguageService +{ + /// + /// Returns the human-readable name of a language. + /// + /// The alias of the language. + /// The human-readable name, or if the name could not be found. + string GetLanguageName(string alias); +} diff --git a/OliverBooth/Services/IProjectService.cs b/OliverBooth.Common/Services/IProjectService.cs similarity index 97% rename from OliverBooth/Services/IProjectService.cs rename to OliverBooth.Common/Services/IProjectService.cs index 16ae771..66cdbaa 100644 --- a/OliverBooth/Services/IProjectService.cs +++ b/OliverBooth.Common/Services/IProjectService.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using OliverBooth.Data.Web; +using OliverBooth.Common.Data.Web; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; /// /// Represents a service for interacting with projects. diff --git a/OliverBooth/Services/IReadingListService.cs b/OliverBooth.Common/Services/IReadingListService.cs similarity index 85% rename from OliverBooth/Services/IReadingListService.cs rename to OliverBooth.Common/Services/IReadingListService.cs index 78cc4f2..6f59791 100644 --- a/OliverBooth/Services/IReadingListService.cs +++ b/OliverBooth.Common/Services/IReadingListService.cs @@ -1,6 +1,6 @@ -using OliverBooth.Data.Web; +using OliverBooth.Common.Data.Web; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; /// /// Represents a service which fetches books from the reading list. diff --git a/OliverBooth/Services/ITutorialService.cs b/OliverBooth.Common/Services/ITutorialService.cs similarity index 97% rename from OliverBooth/Services/ITutorialService.cs rename to OliverBooth.Common/Services/ITutorialService.cs index 1366172..11ddc62 100644 --- a/OliverBooth/Services/ITutorialService.cs +++ b/OliverBooth.Common/Services/ITutorialService.cs @@ -1,9 +1,9 @@ using System.Diagnostics.CodeAnalysis; -using OliverBooth.Data; -using OliverBooth.Data.Blog; -using OliverBooth.Data.Web; +using OliverBooth.Common.Data; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Data.Web; -namespace OliverBooth.Services; +namespace OliverBooth.Common.Services; /// /// Represents a service which can retrieve tutorial articles. diff --git a/OliverBooth/Markdown/Callout/CalloutBlock.cs b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutBlock.cs similarity index 95% rename from OliverBooth/Markdown/Callout/CalloutBlock.cs rename to OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutBlock.cs index 5c01675..fdf484a 100644 --- a/OliverBooth/Markdown/Callout/CalloutBlock.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutBlock.cs @@ -1,7 +1,7 @@ using Markdig.Helpers; using Markdig.Syntax; -namespace OliverBooth.Markdown.Callout; +namespace OliverBooth.Extensions.Markdig.Markdown.Callout; /// /// Represents a callout block. diff --git a/OliverBooth/Markdown/Callout/CalloutExtension.cs b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutExtension.cs similarity index 94% rename from OliverBooth/Markdown/Callout/CalloutExtension.cs rename to OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutExtension.cs index c9731a2..eda4718 100644 --- a/OliverBooth/Markdown/Callout/CalloutExtension.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutExtension.cs @@ -3,7 +3,7 @@ using Markdig.Parsers.Inlines; using Markdig.Renderers; using Markdig.Renderers.Html; -namespace OliverBooth.Markdown.Callout; +namespace OliverBooth.Extensions.Markdig.Markdown.Callout; /// /// Extension for adding Obsidian-style callouts to a Markdown pipeline. diff --git a/OliverBooth/Markdown/Callout/CalloutInlineParser.cs b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutInlineParser.cs similarity index 98% rename from OliverBooth/Markdown/Callout/CalloutInlineParser.cs rename to OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutInlineParser.cs index ce7ddc5..9434011 100644 --- a/OliverBooth/Markdown/Callout/CalloutInlineParser.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutInlineParser.cs @@ -5,7 +5,7 @@ using Markdig.Parsers; using Markdig.Renderers.Html; using Markdig.Syntax; -namespace OliverBooth.Markdown.Callout; +namespace OliverBooth.Extensions.Markdig.Markdown.Callout; /// /// An inline parser for Obsidian-style callouts ([!NOTE] etc.) diff --git a/OliverBooth/Markdown/Callout/CalloutRenderer.cs b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutRenderer.cs similarity index 96% rename from OliverBooth/Markdown/Callout/CalloutRenderer.cs rename to OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutRenderer.cs index f2e4f77..49dd2f2 100644 --- a/OliverBooth/Markdown/Callout/CalloutRenderer.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutRenderer.cs @@ -4,7 +4,7 @@ using Markdig; using Markdig.Renderers; using Markdig.Renderers.Html; -namespace OliverBooth.Markdown.Callout; +namespace OliverBooth.Extensions.Markdig.Markdown.Callout; /// /// Represents an HTML renderer which renders a . @@ -96,7 +96,7 @@ internal sealed class CalloutRenderer : HtmlObjectRenderer private static void WriteTitle(TextRendererBase renderer, MarkdownPipeline pipeline, string calloutTitle) { - string html = Markdig.Markdown.ToHtml(calloutTitle, pipeline); + string html = global::Markdig.Markdown.ToHtml(calloutTitle, pipeline); var document = new HtmlDocument(); document.LoadHtml(html); if (document.DocumentNode.FirstChild is { Name: "p" } child) diff --git a/OliverBooth/Markdown/Template/TemplateExtension.cs b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateExtension.cs similarity index 90% rename from OliverBooth/Markdown/Template/TemplateExtension.cs rename to OliverBooth.Extensions.Markdig/Markdown/Template/TemplateExtension.cs index ec1ea0d..c160735 100644 --- a/OliverBooth/Markdown/Template/TemplateExtension.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateExtension.cs @@ -1,8 +1,8 @@ using Markdig; using Markdig.Renderers; -using OliverBooth.Services; +using OliverBooth.Extensions.Markdig.Services; -namespace OliverBooth.Markdown.Template; +namespace OliverBooth.Extensions.Markdig.Markdown.Template; /// /// Represents a Markdown extension that adds support for MediaWiki-style templates. diff --git a/OliverBooth/Markdown/Template/TemplateInline.cs b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateInline.cs similarity index 95% rename from OliverBooth/Markdown/Template/TemplateInline.cs rename to OliverBooth.Extensions.Markdig/Markdown/Template/TemplateInline.cs index a50f3b8..6b19845 100644 --- a/OliverBooth/Markdown/Template/TemplateInline.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateInline.cs @@ -1,6 +1,6 @@ using Markdig.Syntax.Inlines; -namespace OliverBooth.Markdown.Template; +namespace OliverBooth.Extensions.Markdig.Markdown.Template; /// /// Represents a Markdown inline element that represents a MediaWiki-style template. diff --git a/OliverBooth/Markdown/Template/TemplateInlineParser.cs b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateInlineParser.cs similarity index 99% rename from OliverBooth/Markdown/Template/TemplateInlineParser.cs rename to OliverBooth.Extensions.Markdig/Markdown/Template/TemplateInlineParser.cs index 57385b3..953f415 100644 --- a/OliverBooth/Markdown/Template/TemplateInlineParser.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateInlineParser.cs @@ -2,7 +2,7 @@ using Cysharp.Text; using Markdig.Helpers; using Markdig.Parsers; -namespace OliverBooth.Markdown.Template; +namespace OliverBooth.Extensions.Markdig.Markdown.Template; /// /// Represents a Markdown inline parser that handles MediaWiki-style templates. diff --git a/OliverBooth/Markdown/Template/TemplateRenderer.cs b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateRenderer.cs similarity index 88% rename from OliverBooth/Markdown/Template/TemplateRenderer.cs rename to OliverBooth.Extensions.Markdig/Markdown/Template/TemplateRenderer.cs index 9e70fb2..1c71480 100644 --- a/OliverBooth/Markdown/Template/TemplateRenderer.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Template/TemplateRenderer.cs @@ -1,8 +1,8 @@ using Markdig.Renderers; using Markdig.Renderers.Html; -using OliverBooth.Services; +using OliverBooth.Extensions.Markdig.Services; -namespace OliverBooth.Markdown.Template; +namespace OliverBooth.Extensions.Markdig.Markdown.Template; /// /// Represents a Markdown object renderer that handles elements. diff --git a/OliverBooth/Markdown/Timestamp/TimestampExtension.cs b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampExtension.cs similarity index 91% rename from OliverBooth/Markdown/Timestamp/TimestampExtension.cs rename to OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampExtension.cs index 45ae2df..0a9dac3 100644 --- a/OliverBooth/Markdown/Timestamp/TimestampExtension.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampExtension.cs @@ -1,7 +1,7 @@ using Markdig; using Markdig.Renderers; -namespace OliverBooth.Markdown.Timestamp; +namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp; /// /// Represents a Markdig extension that supports Discord-style timestamps. diff --git a/OliverBooth/Markdown/Timestamp/TimestampFormat.cs b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampFormat.cs similarity index 93% rename from OliverBooth/Markdown/Timestamp/TimestampFormat.cs rename to OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampFormat.cs index 29eca6b..49530be 100644 --- a/OliverBooth/Markdown/Timestamp/TimestampFormat.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampFormat.cs @@ -1,4 +1,4 @@ -namespace OliverBooth.Markdown.Timestamp; +namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp; /// /// An enumeration of timestamp formats. diff --git a/OliverBooth/Markdown/Timestamp/TimestampInline.cs b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampInline.cs similarity index 89% rename from OliverBooth/Markdown/Timestamp/TimestampInline.cs rename to OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampInline.cs index 615ee03..d780ad9 100644 --- a/OliverBooth/Markdown/Timestamp/TimestampInline.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampInline.cs @@ -1,6 +1,6 @@ using Markdig.Syntax.Inlines; -namespace OliverBooth.Markdown.Timestamp; +namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp; /// /// Represents a Markdown inline element that contains a timestamp. diff --git a/OliverBooth/Markdown/Timestamp/TimestampInlineParser.cs b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampInlineParser.cs similarity index 97% rename from OliverBooth/Markdown/Timestamp/TimestampInlineParser.cs rename to OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampInlineParser.cs index 414fc48..ceaaec5 100644 --- a/OliverBooth/Markdown/Timestamp/TimestampInlineParser.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampInlineParser.cs @@ -1,7 +1,7 @@ using Markdig.Helpers; using Markdig.Parsers; -namespace OliverBooth.Markdown.Timestamp; +namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp; /// /// Represents a Markdown inline parser that matches Discord-style timestamps. diff --git a/OliverBooth/Markdown/Timestamp/TimestampRenderer.cs b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampRenderer.cs similarity index 96% rename from OliverBooth/Markdown/Timestamp/TimestampRenderer.cs rename to OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampRenderer.cs index b2c7f3d..ceda5ac 100644 --- a/OliverBooth/Markdown/Timestamp/TimestampRenderer.cs +++ b/OliverBooth.Extensions.Markdig/Markdown/Timestamp/TimestampRenderer.cs @@ -3,7 +3,7 @@ using Humanizer; using Markdig.Renderers; using Markdig.Renderers.Html; -namespace OliverBooth.Markdown.Timestamp; +namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp; /// /// Represents a Markdown object renderer that renders elements. diff --git a/OliverBooth.Extensions.Markdig/MarkdownPipelineExtensions.cs b/OliverBooth.Extensions.Markdig/MarkdownPipelineExtensions.cs new file mode 100644 index 0000000..5426937 --- /dev/null +++ b/OliverBooth.Extensions.Markdig/MarkdownPipelineExtensions.cs @@ -0,0 +1,56 @@ +using Markdig; +using OliverBooth.Extensions.Markdig.Markdown.Callout; +using OliverBooth.Extensions.Markdig.Markdown.Template; +using OliverBooth.Extensions.Markdig.Services; + +namespace OliverBooth.Extensions.Markdig; + +/// +/// Extension methods for . +/// +public static class MarkdownPipelineExtensions +{ + /// + /// Enables the use of Obsidian-style callouts in this pipeline. + /// + /// The Markdig markdown pipeline builder. + /// The modified Markdig markdown pipeline builder. + /// is . + public static MarkdownPipelineBuilder UseCallouts(this MarkdownPipelineBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Extensions.AddIfNotAlready(); + return builder; + } + + /// + /// Enables the use of Wiki-style templates in this pipeline. + /// + /// The Markdig markdown pipeline builder. + /// The template service responsible for fetching and rendering templates. + /// The modified Markdig markdown pipeline builder. + /// + /// is . + /// -or- + /// is . + /// + 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; + } +} diff --git a/OliverBooth.Extensions.Markdig/OliverBooth.Extensions.Markdig.csproj b/OliverBooth.Extensions.Markdig/OliverBooth.Extensions.Markdig.csproj new file mode 100644 index 0000000..5f35f98 --- /dev/null +++ b/OliverBooth.Extensions.Markdig/OliverBooth.Extensions.Markdig.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/OliverBooth/Services/ITemplateService.cs b/OliverBooth.Extensions.Markdig/Services/ITemplateService.cs similarity index 94% rename from OliverBooth/Services/ITemplateService.cs rename to OliverBooth.Extensions.Markdig/Services/ITemplateService.cs index 5fa9239..30c8b86 100644 --- a/OliverBooth/Services/ITemplateService.cs +++ b/OliverBooth.Extensions.Markdig/Services/ITemplateService.cs @@ -1,8 +1,8 @@ using System.Diagnostics.CodeAnalysis; -using OliverBooth.Data.Web; -using OliverBooth.Markdown.Template; +using OliverBooth.Common.Data.Web; +using OliverBooth.Extensions.Markdig.Markdown.Template; -namespace OliverBooth.Services; +namespace OliverBooth.Extensions.Markdig.Services; /// /// Represents a service that renders MediaWiki-style templates. diff --git a/OliverBooth/Formatting/DateFormatter.cs b/OliverBooth.Extensions.SmartFormat/DateFormatter.cs similarity index 95% rename from OliverBooth/Formatting/DateFormatter.cs rename to OliverBooth.Extensions.SmartFormat/DateFormatter.cs index 6109cab..7b98f26 100644 --- a/OliverBooth/Formatting/DateFormatter.cs +++ b/OliverBooth.Extensions.SmartFormat/DateFormatter.cs @@ -1,7 +1,7 @@ using System.Globalization; using SmartFormat.Core.Extensions; -namespace OliverBooth.Formatting; +namespace OliverBooth.Extensions.SmartFormat; /// /// Represents a SmartFormat formatter that formats a date. diff --git a/OliverBooth/Formatting/MarkdownFormatter.cs b/OliverBooth.Extensions.SmartFormat/MarkdownFormatter.cs similarity index 92% rename from OliverBooth/Formatting/MarkdownFormatter.cs rename to OliverBooth.Extensions.SmartFormat/MarkdownFormatter.cs index c682e2c..2f38c96 100644 --- a/OliverBooth/Formatting/MarkdownFormatter.cs +++ b/OliverBooth.Extensions.SmartFormat/MarkdownFormatter.cs @@ -1,7 +1,8 @@ using Markdig; +using Microsoft.Extensions.DependencyInjection; using SmartFormat.Core.Extensions; -namespace OliverBooth.Formatting; +namespace OliverBooth.Extensions.SmartFormat; /// /// Represents a SmartFormat formatter that formats markdown. diff --git a/OliverBooth.Extensions.SmartFormat/OliverBooth.Extensions.SmartFormat.csproj b/OliverBooth.Extensions.SmartFormat/OliverBooth.Extensions.SmartFormat.csproj new file mode 100644 index 0000000..af76520 --- /dev/null +++ b/OliverBooth.Extensions.SmartFormat/OliverBooth.Extensions.SmartFormat.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/OliverBooth.sln b/OliverBooth.sln index e2ba2dd..1d052f5 100644 --- a/OliverBooth.sln +++ b/OliverBooth.sln @@ -13,6 +13,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json EndProjectSection 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 GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,6 +29,18 @@ Global {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.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 GlobalSection(NestedProjects) = preSolution EndGlobalSection diff --git a/OliverBooth/Controllers/Blog/BlogApiController.cs b/OliverBooth/Controllers/Blog/BlogApiController.cs deleted file mode 100644 index 21eb070..0000000 --- a/OliverBooth/Controllers/Blog/BlogApiController.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Humanizer; -using Microsoft.AspNetCore.Mvc; -using OliverBooth.Data.Blog; -using OliverBooth.Services; - -namespace OliverBooth.Controllers.Blog; - -/// -/// Represents a controller for the blog API. -/// -[ApiController] -[Route("api/blog")] -[Produces("application/json")] -public sealed class BlogApiController : ControllerBase -{ - private readonly IBlogPostService _blogPostService; - private readonly IBlogUserService _userService; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - 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 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 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 - } - }; - } -} diff --git a/OliverBooth/Controllers/Blog/RssController.cs b/OliverBooth/Controllers/Blog/RssController.cs index 83b1a08..04cd16d 100644 --- a/OliverBooth/Controllers/Blog/RssController.cs +++ b/OliverBooth/Controllers/Blog/RssController.cs @@ -1,8 +1,8 @@ using System.Xml.Serialization; using Microsoft.AspNetCore.Mvc; -using OliverBooth.Data.Blog; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Services; using OliverBooth.Data.Blog.Rss; -using OliverBooth.Services; namespace OliverBooth.Controllers.Blog; diff --git a/OliverBooth/Controllers/FormattedBlacklist.cs b/OliverBooth/Controllers/FormattedBlacklist.cs index bce6143..5186ec9 100644 --- a/OliverBooth/Controllers/FormattedBlacklist.cs +++ b/OliverBooth/Controllers/FormattedBlacklist.cs @@ -1,7 +1,7 @@ using System.Text; using Microsoft.AspNetCore.Mvc; -using OliverBooth.Data.Web; -using OliverBooth.Services; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; namespace OliverBooth.Controllers; diff --git a/OliverBooth/Data/Blog/BlogPost.cs b/OliverBooth/Data/Blog/BlogPost.cs index 59067b5..f84292a 100644 --- a/OliverBooth/Data/Blog/BlogPost.cs +++ b/OliverBooth/Data/Blog/BlogPost.cs @@ -1,4 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; +using OliverBooth.Common.Data; +using OliverBooth.Common.Data.Blog; using SmartFormat; namespace OliverBooth.Data.Blog; diff --git a/OliverBooth/Data/Blog/Configuration/BlogPostConfiguration.cs b/OliverBooth/Data/Blog/Configuration/BlogPostConfiguration.cs index 1ecde2f..fd7085b 100644 --- a/OliverBooth/Data/Blog/Configuration/BlogPostConfiguration.cs +++ b/OliverBooth/Data/Blog/Configuration/BlogPostConfiguration.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OliverBooth.Common.Data; namespace OliverBooth.Data.Blog.Configuration; diff --git a/OliverBooth/Data/Blog/LegacyComment.cs b/OliverBooth/Data/Blog/LegacyComment.cs index a020979..cd8b615 100644 --- a/OliverBooth/Data/Blog/LegacyComment.cs +++ b/OliverBooth/Data/Blog/LegacyComment.cs @@ -1,4 +1,5 @@ using System.Web; +using OliverBooth.Common.Data.Blog; namespace OliverBooth.Data.Blog; diff --git a/OliverBooth/Data/Blog/User.cs b/OliverBooth/Data/Blog/User.cs index dcd50ab..1b6ce40 100644 --- a/OliverBooth/Data/Blog/User.cs +++ b/OliverBooth/Data/Blog/User.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Security.Cryptography; using System.Text; using Cysharp.Text; +using OliverBooth.Common.Data.Blog; namespace OliverBooth.Data.Blog; diff --git a/OliverBooth/Data/Mastodon/MastodonStatus.cs b/OliverBooth/Data/Mastodon/MastodonStatus.cs index a4d3ebf..4276c02 100644 --- a/OliverBooth/Data/Mastodon/MastodonStatus.cs +++ b/OliverBooth/Data/Mastodon/MastodonStatus.cs @@ -1,34 +1,24 @@ using System.Text.Json.Serialization; +using OliverBooth.Common.Data.Mastodon; namespace OliverBooth.Data.Mastodon; -public sealed class MastodonStatus +/// +internal sealed class MastodonStatus : IMastodonStatus { - /// - /// Gets the content of the status. - /// - /// The content. + /// [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; - /// - /// Gets the date and time at which this status was posted. - /// - /// The post timestamp. + /// [JsonPropertyName("created_at")] public DateTimeOffset CreatedAt { get; set; } - /// - /// Gets the media attachments for this status. - /// - /// The media attachments. + /// [JsonPropertyName("media_attachments")] - public IReadOnlyList MediaAttachments { get; set; } = ArraySegment.Empty; + public IReadOnlyList MediaAttachments { get; set; } = ArraySegment.Empty; - /// - /// Gets the original URI of the status. - /// - /// The original URI. + /// [JsonPropertyName("url")] public Uri OriginalUri { get; set; } = null!; } diff --git a/OliverBooth/Data/Web/BlacklistEntry.cs b/OliverBooth/Data/Web/BlacklistEntry.cs index 374d9c2..a0f68d3 100644 --- a/OliverBooth/Data/Web/BlacklistEntry.cs +++ b/OliverBooth/Data/Web/BlacklistEntry.cs @@ -1,3 +1,5 @@ +using OliverBooth.Common.Data.Web; + namespace OliverBooth.Data.Web; /// diff --git a/OliverBooth/Data/Web/Book.cs b/OliverBooth/Data/Web/Book.cs index 278aa4e..10e41dd 100644 --- a/OliverBooth/Data/Web/Book.cs +++ b/OliverBooth/Data/Web/Book.cs @@ -1,4 +1,5 @@ using NetBarcode; +using OliverBooth.Common.Data.Web; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Processing; diff --git a/OliverBooth/Data/Web/CodeSnippet.cs b/OliverBooth/Data/Web/CodeSnippet.cs index 19d29c1..13fb751 100644 --- a/OliverBooth/Data/Web/CodeSnippet.cs +++ b/OliverBooth/Data/Web/CodeSnippet.cs @@ -1,3 +1,5 @@ +using OliverBooth.Common.Data.Web; + namespace OliverBooth.Data.Web; /// diff --git a/OliverBooth/Data/Web/Configuration/BookConfiguration.cs b/OliverBooth/Data/Web/Configuration/BookConfiguration.cs index f27a1d2..fca84f9 100644 --- a/OliverBooth/Data/Web/Configuration/BookConfiguration.cs +++ b/OliverBooth/Data/Web/Configuration/BookConfiguration.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OliverBooth.Common.Data.Web; namespace OliverBooth.Data.Web.Configuration; diff --git a/OliverBooth/Data/Web/Configuration/ProjectConfiguration.cs b/OliverBooth/Data/Web/Configuration/ProjectConfiguration.cs index 08ef5d9..8949763 100644 --- a/OliverBooth/Data/Web/Configuration/ProjectConfiguration.cs +++ b/OliverBooth/Data/Web/Configuration/ProjectConfiguration.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OliverBooth.Common.Data.Web; namespace OliverBooth.Data.Web.Configuration; diff --git a/OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs b/OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs index b86a5b5..4caf190 100644 --- a/OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs +++ b/OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OliverBooth.Common.Data; namespace OliverBooth.Data.Web.Configuration; diff --git a/OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs b/OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs index af1eef2..136d9c3 100644 --- a/OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs +++ b/OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OliverBooth.Common.Data; namespace OliverBooth.Data.Web.Configuration; diff --git a/OliverBooth/Data/Web/ProgrammingLanguage.cs b/OliverBooth/Data/Web/ProgrammingLanguage.cs index 53746d9..9a4135f 100644 --- a/OliverBooth/Data/Web/ProgrammingLanguage.cs +++ b/OliverBooth/Data/Web/ProgrammingLanguage.cs @@ -1,3 +1,5 @@ +using OliverBooth.Common.Data.Web; + namespace OliverBooth.Data.Web; /// diff --git a/OliverBooth/Data/Web/Project.cs b/OliverBooth/Data/Web/Project.cs index 05761a7..37b6b9e 100644 --- a/OliverBooth/Data/Web/Project.cs +++ b/OliverBooth/Data/Web/Project.cs @@ -1,3 +1,5 @@ +using OliverBooth.Common.Data.Web; + namespace OliverBooth.Data.Web; /// diff --git a/OliverBooth/Data/Web/Template.cs b/OliverBooth/Data/Web/Template.cs index cf029a1..29355c5 100644 --- a/OliverBooth/Data/Web/Template.cs +++ b/OliverBooth/Data/Web/Template.cs @@ -1,3 +1,5 @@ +using OliverBooth.Common.Data.Web; + namespace OliverBooth.Data.Web; /// diff --git a/OliverBooth/Data/Web/TutorialArticle.cs b/OliverBooth/Data/Web/TutorialArticle.cs index 43ef096..a1f9801 100644 --- a/OliverBooth/Data/Web/TutorialArticle.cs +++ b/OliverBooth/Data/Web/TutorialArticle.cs @@ -1,4 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; +using OliverBooth.Common.Data; +using OliverBooth.Common.Data.Web; namespace OliverBooth.Data.Web; diff --git a/OliverBooth/Data/Web/TutorialFolder.cs b/OliverBooth/Data/Web/TutorialFolder.cs index 26c5bc1..1f69ef6 100644 --- a/OliverBooth/Data/Web/TutorialFolder.cs +++ b/OliverBooth/Data/Web/TutorialFolder.cs @@ -1,3 +1,6 @@ +using OliverBooth.Common.Data; +using OliverBooth.Common.Data.Web; + namespace OliverBooth.Data.Web; /// diff --git a/OliverBooth/Extensions/HtmlUtility.cs b/OliverBooth/Extensions/HtmlUtility.cs index 94e3d6a..556cb6d 100644 --- a/OliverBooth/Extensions/HtmlUtility.cs +++ b/OliverBooth/Extensions/HtmlUtility.cs @@ -1,8 +1,8 @@ using System.Web; using Cysharp.Text; -using OliverBooth.Data.Blog; -using OliverBooth.Data.Web; -using OliverBooth.Services; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; namespace OliverBooth.Extensions; diff --git a/OliverBooth/Markdown/MarkdownExtensions.cs b/OliverBooth/Markdown/MarkdownExtensions.cs deleted file mode 100644 index d63d869..0000000 --- a/OliverBooth/Markdown/MarkdownExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Markdig; -using OliverBooth.Markdown.Callout; - -namespace OliverBooth.Markdown; - -/// -/// Extension methods for . -/// -internal static class MarkdownExtensions -{ - /// - /// Uses this extension to enable Obsidian-style callouts. - /// - /// The pipeline. - /// The modified pipeline. - public static MarkdownPipelineBuilder UseCallouts(this MarkdownPipelineBuilder pipeline) - { - pipeline.Extensions.AddIfNotAlready(); - return pipeline; - } -} diff --git a/OliverBooth/Markdown/Template/CodeSnippetTemplateRenderer.cs b/OliverBooth/Markdown/Template/CodeSnippetTemplateRenderer.cs index e809801..f93abd8 100644 --- a/OliverBooth/Markdown/Template/CodeSnippetTemplateRenderer.cs +++ b/OliverBooth/Markdown/Template/CodeSnippetTemplateRenderer.cs @@ -1,8 +1,9 @@ using System.Diagnostics; using System.Text; using Markdig; -using OliverBooth.Data.Web; -using OliverBooth.Services; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; +using OliverBooth.Extensions.Markdig.Markdown.Template; namespace OliverBooth.Markdown.Template; diff --git a/OliverBooth/Markdown/Template/CustomTemplateRenderer.cs b/OliverBooth/Markdown/Template/CustomTemplateRenderer.cs index 1d3f122..9fbbc2a 100644 --- a/OliverBooth/Markdown/Template/CustomTemplateRenderer.cs +++ b/OliverBooth/Markdown/Template/CustomTemplateRenderer.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using OliverBooth.Data.Web; +using OliverBooth.Extensions.Markdig.Markdown.Template; namespace OliverBooth.Markdown.Template; diff --git a/OliverBooth/OliverBooth.csproj b/OliverBooth/OliverBooth.csproj index 2922e7a..f822f33 100644 --- a/OliverBooth/OliverBooth.csproj +++ b/OliverBooth/OliverBooth.csproj @@ -30,13 +30,9 @@ - - - - @@ -45,10 +41,20 @@ - - + + + + + + + + + + + _PageTabs.cshtml + diff --git a/OliverBooth/Pages/Blog/Article.cshtml b/OliverBooth/Pages/Blog/Article.cshtml index bdc3ee5..3300621 100644 --- a/OliverBooth/Pages/Blog/Article.cshtml +++ b/OliverBooth/Pages/Blog/Article.cshtml @@ -1,9 +1,10 @@ @page "/blog/{year:int}/{month:int}/{day:int}/{slug}" @using Humanizer @using Markdig -@using OliverBooth.Data -@using OliverBooth.Data.Blog -@using OliverBooth.Services +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OliverBooth.Common.Data +@using OliverBooth.Common.Data.Blog +@using OliverBooth.Common.Services @inject IBlogPostService BlogPostService @inject MarkdownPipeline MarkdownPipeline @model Article @@ -77,12 +78,7 @@ }

- +
@@ -91,6 +87,18 @@
+
+ + +
+
@if (BlogPostService.GetPreviousPost(post) is { } previousPost) diff --git a/OliverBooth/Pages/Blog/Article.cshtml.cs b/OliverBooth/Pages/Blog/Article.cshtml.cs index 17a9337..92315be 100644 --- a/OliverBooth/Pages/Blog/Article.cshtml.cs +++ b/OliverBooth/Pages/Blog/Article.cshtml.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Primitives; -using OliverBooth.Data.Blog; -using OliverBooth.Services; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Services; using BC = BCrypt.Net.BCrypt; namespace OliverBooth.Pages.Blog; @@ -79,7 +79,6 @@ public class Article : PageModel var date = new DateOnly(year, month, day); if (!_blogPostService.TryGetPost(date, slug, out IBlogPost? post)) { - Response.StatusCode = 404; return NotFound(); } diff --git a/OliverBooth/Pages/Blog/Index.cshtml b/OliverBooth/Pages/Blog/Index.cshtml index 0229989..876e06f 100644 --- a/OliverBooth/Pages/Blog/Index.cshtml +++ b/OliverBooth/Pages/Blog/Index.cshtml @@ -1,75 +1,26 @@ @page -@using Humanizer -@using OliverBooth.Data.Mastodon -@using OliverBooth.Services +@using OliverBooth.Common.Data +@using OliverBooth.Common.Data.Blog +@using OliverBooth.Common.Services @model Index -@inject IMastodonService MastodonService +@inject IBlogPostService BlogPostService @{ ViewData["Title"] = "Blog"; - MastodonStatus latestStatus = MastodonService.GetLatestStatus(); } -
-
- @Html.Raw(latestStatus.Content) - @foreach (MediaAttachment attachment in latestStatus.MediaAttachments) - { - switch (attachment.Type) - { - case AttachmentType.Audio: -

- break; - - case AttachmentType.Video: -

- break; - - case AttachmentType.Image: - case AttachmentType.GifV: -

- break; - } - } -
- -
+@await Html.PartialAsync("Partials/_MastodonStatus")
- @await Html.PartialAsync("_LoadingSpinner") + @foreach (IBlogPost post in BlogPostService.GetBlogPosts(0)) + { + @await Html.PartialAsync("Partials/_BlogCard", post) + }
- \ No newline at end of file +@await Html.PartialAsync("Partials/_PageTabs", new ViewDataDictionary(ViewData) +{ + ["UrlRoot"] = "/blog", + ["Page"] = 1, + ["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published) +}) \ No newline at end of file diff --git a/OliverBooth/Pages/Blog/Index.cshtml.cs b/OliverBooth/Pages/Blog/Index.cshtml.cs index 97104b6..378ceb2 100644 --- a/OliverBooth/Pages/Blog/Index.cshtml.cs +++ b/OliverBooth/Pages/Blog/Index.cshtml.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using OliverBooth.Data.Blog; -using OliverBooth.Services; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Services; namespace OliverBooth.Pages.Blog; @@ -36,7 +36,7 @@ public class Index : PageModel return _blogPostService.TryGetPost(wpPostId, out IBlogPost? post) ? RedirectToPost(post) : NotFound(); } - private IActionResult RedirectToPost(IBlogPost post) + private RedirectResult RedirectToPost(IBlogPost post) { var route = new { diff --git a/OliverBooth/Pages/Blog/List.cshtml b/OliverBooth/Pages/Blog/List.cshtml new file mode 100644 index 0000000..61fb3d8 --- /dev/null +++ b/OliverBooth/Pages/Blog/List.cshtml @@ -0,0 +1,23 @@ +@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") + +
+ @foreach (IBlogPost post in BlogPostService.GetBlogPosts(Model.PageNumber)) + { + @await Html.PartialAsync("Partials/_BlogCard", post) + } +
+ +@await Html.PartialAsync("Partials/_PageTabs", new ViewDataDictionary(ViewData) +{ + ["UrlRoot"] = "/blog", + ["Page"] = Model.PageNumber, + ["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published) +}) \ No newline at end of file diff --git a/OliverBooth/Pages/Blog/List.cshtml.cs b/OliverBooth/Pages/Blog/List.cshtml.cs new file mode 100644 index 0000000..f0aa6dd --- /dev/null +++ b/OliverBooth/Pages/Blog/List.cshtml.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace OliverBooth.Pages.Blog; + +/// +/// Represents a class which defines the model for the /blog/page/# route. +/// +public class List : PageModel +{ + /// + /// Gets the requested page number. + /// + /// The requested page number. + public int PageNumber { get; private set; } + + /// + /// Handles the incoming GET request to the page. + /// + /// The requested page number, starting from 1. + /// + public IActionResult OnGet([FromRoute(Name = "pageNumber")] int page = 1) + { + if (page < 2) + { + return RedirectToPage("Index"); + } + + PageNumber = page; + return Page(); + } +} diff --git a/OliverBooth/Pages/Blog/RawArticle.cshtml.cs b/OliverBooth/Pages/Blog/RawArticle.cshtml.cs index 1fc34eb..adbd374 100644 --- a/OliverBooth/Pages/Blog/RawArticle.cshtml.cs +++ b/OliverBooth/Pages/Blog/RawArticle.cshtml.cs @@ -1,8 +1,8 @@ using Cysharp.Text; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using OliverBooth.Data.Blog; -using OliverBooth.Services; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Services; namespace OliverBooth.Pages.Blog; diff --git a/OliverBooth/Pages/Books.cshtml b/OliverBooth/Pages/Books.cshtml index dd83d7e..f7bbb79 100644 --- a/OliverBooth/Pages/Books.cshtml +++ b/OliverBooth/Pages/Books.cshtml @@ -1,5 +1,5 @@ @page -@using OliverBooth.Data.Web +@using OliverBooth.Common.Data.Web @model OliverBooth.Pages.Books @{ ViewData["Title"] = "Reading List"; diff --git a/OliverBooth/Pages/Books.cshtml.cs b/OliverBooth/Pages/Books.cshtml.cs index 6cbc5af..6668ed3 100644 --- a/OliverBooth/Pages/Books.cshtml.cs +++ b/OliverBooth/Pages/Books.cshtml.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -using OliverBooth.Data.Web; -using OliverBooth.Services; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; namespace OliverBooth.Pages; diff --git a/OliverBooth/Pages/Contact/Blacklist.cshtml b/OliverBooth/Pages/Contact/Blacklist.cshtml index a0101ac..20feb7a 100644 --- a/OliverBooth/Pages/Contact/Blacklist.cshtml +++ b/OliverBooth/Pages/Contact/Blacklist.cshtml @@ -1,6 +1,7 @@ @page -@using OliverBooth.Data.Web -@using OliverBooth.Services +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OliverBooth.Common.Data.Web +@using OliverBooth.Common.Services @inject IContactService ContactService @{ ViewData["Title"] = "Blacklist"; diff --git a/OliverBooth/Pages/Contact/Index.cshtml b/OliverBooth/Pages/Contact/Index.cshtml index 12d5f70..95bdd86 100644 --- a/OliverBooth/Pages/Contact/Index.cshtml +++ b/OliverBooth/Pages/Contact/Index.cshtml @@ -1,4 +1,5 @@ @page +@using Microsoft.AspNetCore.Mvc.TagHelpers @{ ViewData["Title"] = "Contact"; } diff --git a/OliverBooth/Pages/Contact/Result.cshtml b/OliverBooth/Pages/Contact/Result.cshtml index 65de504..bc5b79d 100644 --- a/OliverBooth/Pages/Contact/Result.cshtml +++ b/OliverBooth/Pages/Contact/Result.cshtml @@ -1,4 +1,5 @@ @page +@using Microsoft.AspNetCore.Mvc.TagHelpers @model OliverBooth.Pages.Contact.Result @{ diff --git a/OliverBooth/Pages/Error.cshtml b/OliverBooth/Pages/Error.cshtml deleted file mode 100644 index b7d6b09..0000000 --- a/OliverBooth/Pages/Error.cshtml +++ /dev/null @@ -1,34 +0,0 @@ -@page "/error/{code:int?}" -@model OliverBooth.Pages.ErrorModel -@{ - Layout = "_MinimalLayout"; - ViewData["Title"] = "Error"; -} - -

- @switch (Model.HttpStatusCode) - { - case 403: - 403 Forbidden - break; - - case 404: - 404 Page not found - break; - - case 500: - Internal server error - break; - - default: - Something went wrong - break; - } -

- -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} \ No newline at end of file diff --git a/OliverBooth/Pages/Error.cshtml.cs b/OliverBooth/Pages/Error.cshtml.cs deleted file mode 100644 index 04f1916..0000000 --- a/OliverBooth/Pages/Error.cshtml.cs +++ /dev/null @@ -1,28 +0,0 @@ -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(); - } -} diff --git a/OliverBooth/Pages/Error/BadRequest.cshtml b/OliverBooth/Pages/Error/BadRequest.cshtml new file mode 100644 index 0000000..d1e3fbc --- /dev/null +++ b/OliverBooth/Pages/Error/BadRequest.cshtml @@ -0,0 +1,17 @@ +@page "/error/400" +
+
+
+
+

400 Bad Request

+
+
+

Received invalid request message. Check your request and try again.

+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/Forbidden.cshtml b/OliverBooth/Pages/Error/Forbidden.cshtml new file mode 100644 index 0000000..2fd5597 --- /dev/null +++ b/OliverBooth/Pages/Error/Forbidden.cshtml @@ -0,0 +1,15 @@ +@page "/error/403" +
+
+
+

403 Forbidden

+
+
+ +
+ +
+

Access to the requested page is forbidden.

+
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/GatewayTimeout.cshtml b/OliverBooth/Pages/Error/GatewayTimeout.cshtml new file mode 100644 index 0000000..b44d160 --- /dev/null +++ b/OliverBooth/Pages/Error/GatewayTimeout.cshtml @@ -0,0 +1,17 @@ +@page "/error/504" +
+
+
+ +
+ +
+
+

504 Gateway Timeout

+
+
+

The server is slacking. Give it more coffee.

+
+
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/Gone.cshtml b/OliverBooth/Pages/Error/Gone.cshtml new file mode 100644 index 0000000..dc5084a --- /dev/null +++ b/OliverBooth/Pages/Error/Gone.cshtml @@ -0,0 +1,17 @@ +@page "/error/400" +
+
+
+
+

410 Gone

+
+
+

The requested page has mysteriously disappeared.

+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/ImATeapot.cshtml b/OliverBooth/Pages/Error/ImATeapot.cshtml new file mode 100644 index 0000000..c53f758 --- /dev/null +++ b/OliverBooth/Pages/Error/ImATeapot.cshtml @@ -0,0 +1,17 @@ +@page "/error/418" +
+
+
+ +
+ +
+
+

418 I'm A Teapot

+
+
+

No coffee available. I am only capable of brewing tea.

+
+
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/InternalServerError.cshtml b/OliverBooth/Pages/Error/InternalServerError.cshtml new file mode 100644 index 0000000..27b4605 --- /dev/null +++ b/OliverBooth/Pages/Error/InternalServerError.cshtml @@ -0,0 +1,17 @@ +@page "/error/500" +
+
+
+ +
+ +
+
+

500 Internal Server Error

+
+
+

This is my fault, not yours.

+
+
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/NotFound.cshtml b/OliverBooth/Pages/Error/NotFound.cshtml new file mode 100644 index 0000000..cb54df4 --- /dev/null +++ b/OliverBooth/Pages/Error/NotFound.cshtml @@ -0,0 +1,17 @@ +@page "/error/404" +
+
+
+ +
+ +
+
+

404 Not Found

+
+
+

The requested page could not be found.

+
+
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/ServiceUnavailable.cshtml b/OliverBooth/Pages/Error/ServiceUnavailable.cshtml new file mode 100644 index 0000000..a33a4de --- /dev/null +++ b/OliverBooth/Pages/Error/ServiceUnavailable.cshtml @@ -0,0 +1,17 @@ +@page "/error/503" +
+
+
+
+

503 Service Unavailable

+
+
+

The server is currently unable to process your request. Please try again later.

+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Error/TooManyRequests.cshtml b/OliverBooth/Pages/Error/TooManyRequests.cshtml new file mode 100644 index 0000000..046b860 --- /dev/null +++ b/OliverBooth/Pages/Error/TooManyRequests.cshtml @@ -0,0 +1,17 @@ +@page "/error/429" +
+
+
+
+

429 Too Many Requests

+
+
+

You are being rate limited.

+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/OliverBooth/Pages/Index.cshtml b/OliverBooth/Pages/Index.cshtml index 0f85b37..89821cc 100644 --- a/OliverBooth/Pages/Index.cshtml +++ b/OliverBooth/Pages/Index.cshtml @@ -1,4 +1,5 @@ @page +@using Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/OliverBooth/Pages/Privacy/FiveOClockSomewhere.cshtml b/OliverBooth/Pages/Privacy/FiveOClockSomewhere.cshtml index 5c5365a..07fe6a5 100644 --- a/OliverBooth/Pages/Privacy/FiveOClockSomewhere.cshtml +++ b/OliverBooth/Pages/Privacy/FiveOClockSomewhere.cshtml @@ -1,4 +1,5 @@ @page "/privacy/five-oclock-somewhere" +@using Microsoft.AspNetCore.Mvc.TagHelpers @{ ViewData["Title"] = "It's 5 O'Clock Somewhere Privacy Policy"; } diff --git a/OliverBooth/Pages/Privacy/GooglePlay.cshtml b/OliverBooth/Pages/Privacy/GooglePlay.cshtml index 37caca9..9d340c5 100644 --- a/OliverBooth/Pages/Privacy/GooglePlay.cshtml +++ b/OliverBooth/Pages/Privacy/GooglePlay.cshtml @@ -1,4 +1,5 @@ @page "/privacy/google-play" +@using Microsoft.AspNetCore.Mvc.TagHelpers @{ ViewData["Title"] = "Google Play Privacy Policy"; } diff --git a/OliverBooth/Pages/Privacy/Index.cshtml b/OliverBooth/Pages/Privacy/Index.cshtml index d4afbd3..d5ef608 100644 --- a/OliverBooth/Pages/Privacy/Index.cshtml +++ b/OliverBooth/Pages/Privacy/Index.cshtml @@ -1,4 +1,5 @@ @page +@using Microsoft.AspNetCore.Mvc.TagHelpers @{ ViewData["Title"] = "Privacy Policy"; } diff --git a/OliverBooth/Pages/Projects/Index.cshtml b/OliverBooth/Pages/Projects/Index.cshtml index 90468e5..a9e8f77 100644 --- a/OliverBooth/Pages/Projects/Index.cshtml +++ b/OliverBooth/Pages/Projects/Index.cshtml @@ -1,6 +1,7 @@ @page -@using OliverBooth.Data.Web -@using OliverBooth.Services +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OliverBooth.Common.Data.Web +@using OliverBooth.Common.Services @inject IProjectService ProjectService @{ ViewData["Title"] = "Projects"; diff --git a/OliverBooth/Pages/Projects/Project.cshtml b/OliverBooth/Pages/Projects/Project.cshtml index b9c9933..80141dc 100644 --- a/OliverBooth/Pages/Projects/Project.cshtml +++ b/OliverBooth/Pages/Projects/Project.cshtml @@ -1,7 +1,8 @@ @page "/project/{slug}" @using Markdig -@using OliverBooth.Data.Web -@using OliverBooth.Services +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OliverBooth.Common.Data.Web +@using OliverBooth.Common.Services @model Project @inject IProjectService ProjectService @inject MarkdownPipeline MarkdownPipeline diff --git a/OliverBooth/Pages/Projects/Project.cshtml.cs b/OliverBooth/Pages/Projects/Project.cshtml.cs index 87782ec..a762e6d 100644 --- a/OliverBooth/Pages/Projects/Project.cshtml.cs +++ b/OliverBooth/Pages/Projects/Project.cshtml.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -using OliverBooth.Data.Web; -using OliverBooth.Services; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; namespace OliverBooth.Pages.Projects; diff --git a/OliverBooth/Pages/Shared/Partials/PageTabsUtility.cs b/OliverBooth/Pages/Shared/Partials/PageTabsUtility.cs new file mode 100644 index 0000000..833cd77 --- /dev/null +++ b/OliverBooth/Pages/Shared/Partials/PageTabsUtility.cs @@ -0,0 +1,205 @@ +using Cysharp.Text; +using HtmlAgilityPack; + +namespace OliverBooth.Pages.Shared.Partials; + +/// +/// Provides methods for displaying pagination tabs. +/// +public class PageTabsUtility +{ + private string _urlRoot = string.Empty; + + /// + /// Gets or sets the current page number. + /// + /// The current page number. + public int CurrentPage { get; set; } = 1; + + /// + /// Gets or sets the page count. + /// + /// The page count. + public int PageCount { get; set; } = 1; + + /// + /// Gets or sets the URL root. + /// + /// The URL root. + public string UrlRoot + { + get => _urlRoot; + set => _urlRoot = string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim(); + } + + /// + /// Shows the bound chevrons for the specified bounds type. + /// + /// The bounds type to display. + /// An HTML string containing the elements representing the bound chevrons. + public string ShowBounds(BoundsType bounds) + { + return bounds switch + { + BoundsType.Lower => ShowLowerBound(), + BoundsType.Upper => ShowUpperBound(PageCount), + _ => string.Empty + }; + } + + /// + /// Shows the specified page tab. + /// + /// The tab to display. + /// An HTML string containing the element for the specified page tab. + 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; + } + + /// + /// Shows the paginated tab window. + /// + /// An HTML string representing the page tabs. + 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 + } +} diff --git a/OliverBooth/Pages/Shared/Partials/_BlogCard.cshtml b/OliverBooth/Pages/Shared/Partials/_BlogCard.cshtml new file mode 100644 index 0000000..134f78a --- /dev/null +++ b/OliverBooth/Pages/Shared/Partials/_BlogCard.cshtml @@ -0,0 +1,65 @@ +@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"; +} + +
+

+ + @Model.Title + +

+ +

+ @author.DisplayName + @author.DisplayName + • + @verb @time.Humanize() +

+ +
+ @Html.Raw(BlogPostService.RenderExcerpt(Model, out bool trimmed)) +
+ + @if (trimmed || Model.Excerpt is not null) + { +

+ + Read more... + +

+ } + +
+ +
+ + +
+ +
\ No newline at end of file diff --git a/OliverBooth/Pages/Shared/Partials/_MastodonStatus.cshtml b/OliverBooth/Pages/Shared/Partials/_MastodonStatus.cshtml new file mode 100644 index 0000000..0a309ad --- /dev/null +++ b/OliverBooth/Pages/Shared/Partials/_MastodonStatus.cshtml @@ -0,0 +1,42 @@ +@using Humanizer +@using OliverBooth.Common.Data.Mastodon +@using OliverBooth.Common.Services +@inject IMastodonService MastodonService +@{ + IMastodonStatus latestStatus = MastodonService.GetLatestStatus(); +} + +
+
+ @Html.Raw(latestStatus.Content) + @foreach (MediaAttachment attachment in latestStatus.MediaAttachments) + { + switch (attachment.Type) + { + case AttachmentType.Audio: +

+ +

+ break; + + case AttachmentType.Video: +

+ +

+ break; + + case AttachmentType.Image: + case AttachmentType.GifV: +

+ +

+ break; + } + } +
+ +
diff --git a/OliverBooth/Pages/Shared/Partials/_PageTabs.cshtml b/OliverBooth/Pages/Shared/Partials/_PageTabs.cshtml new file mode 100644 index 0000000..6293a4f --- /dev/null +++ b/OliverBooth/Pages/Shared/Partials/_PageTabs.cshtml @@ -0,0 +1,21 @@ +@{ + 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 + }; +} + \ No newline at end of file diff --git a/OliverBooth/Pages/Shared/_Layout.cshtml b/OliverBooth/Pages/Shared/_Layout.cshtml index db84eee..4622e8f 100644 --- a/OliverBooth/Pages/Shared/_Layout.cshtml +++ b/OliverBooth/Pages/Shared/_Layout.cshtml @@ -1,7 +1,8 @@ -@using OliverBooth.Data.Blog -@using OliverBooth.Data.Web +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OliverBooth.Common.Data.Blog +@using OliverBooth.Common.Data.Web +@using OliverBooth.Common.Services @using OliverBooth.Extensions -@using OliverBooth.Services @inject IBlogPostService BlogPostService @inject ITutorialService TutorialService @{ diff --git a/OliverBooth/Pages/Shared/_MinimalLayout.cshtml b/OliverBooth/Pages/Shared/_MinimalLayout.cshtml index 48fd1a8..b40e15f 100644 --- a/OliverBooth/Pages/Shared/_MinimalLayout.cshtml +++ b/OliverBooth/Pages/Shared/_MinimalLayout.cshtml @@ -1,3 +1,4 @@ +@using Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/OliverBooth/Pages/Tutorials/Article.cshtml b/OliverBooth/Pages/Tutorials/Article.cshtml index 2970273..0433dbb 100644 --- a/OliverBooth/Pages/Tutorials/Article.cshtml +++ b/OliverBooth/Pages/Tutorials/Article.cshtml @@ -2,10 +2,10 @@ @using Humanizer @using Markdig @using Microsoft.AspNetCore.Mvc.TagHelpers -@using OliverBooth.Data -@using OliverBooth.Data.Blog -@using OliverBooth.Data.Web -@using OliverBooth.Services +@using OliverBooth.Common.Data +@using OliverBooth.Common.Data.Blog +@using OliverBooth.Common.Data.Web +@using OliverBooth.Common.Services @inject ITutorialService TutorialService @inject MarkdownPipeline MarkdownPipeline @model Article diff --git a/OliverBooth/Pages/Tutorials/Article.cshtml.cs b/OliverBooth/Pages/Tutorials/Article.cshtml.cs index 7f5f6c7..2b87435 100644 --- a/OliverBooth/Pages/Tutorials/Article.cshtml.cs +++ b/OliverBooth/Pages/Tutorials/Article.cshtml.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using OliverBooth.Data.Web; -using OliverBooth.Services; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; namespace OliverBooth.Pages.Tutorials; diff --git a/OliverBooth/Pages/Tutorials/Index.cshtml b/OliverBooth/Pages/Tutorials/Index.cshtml index aaa2346..06f7a7a 100644 --- a/OliverBooth/Pages/Tutorials/Index.cshtml +++ b/OliverBooth/Pages/Tutorials/Index.cshtml @@ -1,8 +1,9 @@ @page "/tutorials/{**slug}" @using System.Text -@using OliverBooth.Data -@using OliverBooth.Data.Web -@using OliverBooth.Services +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OliverBooth.Common.Data +@using OliverBooth.Common.Data.Web +@using OliverBooth.Common.Services @model Index @inject ITutorialService TutorialService @{ diff --git a/OliverBooth/Pages/Tutorials/Index.cshtml.cs b/OliverBooth/Pages/Tutorials/Index.cshtml.cs index ac08919..23892fd 100644 --- a/OliverBooth/Pages/Tutorials/Index.cshtml.cs +++ b/OliverBooth/Pages/Tutorials/Index.cshtml.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using OliverBooth.Data.Web; -using OliverBooth.Services; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; namespace OliverBooth.Pages.Tutorials; diff --git a/OliverBooth/Program.cs b/OliverBooth/Program.cs index 6360e7a..472e602 100644 --- a/OliverBooth/Program.cs +++ b/OliverBooth/Program.cs @@ -1,12 +1,12 @@ using AspNetCore.ReCaptcha; using Markdig; +using OliverBooth.Common.Services; using OliverBooth.Data.Blog; using OliverBooth.Data.Web; using OliverBooth.Extensions; -using OliverBooth.Markdown; -using OliverBooth.Markdown.Callout; -using OliverBooth.Markdown.Template; -using OliverBooth.Markdown.Timestamp; +using OliverBooth.Extensions.Markdig; +using OliverBooth.Extensions.Markdig.Markdown.Timestamp; +using OliverBooth.Extensions.Markdig.Services; using OliverBooth.Services; using Serilog; @@ -25,7 +25,7 @@ builder.Logging.AddSerilog(); builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder() .Use() - .Use(new TemplateExtension(provider.GetRequiredService())) + .UseTemplates(provider.GetRequiredService()) // we have our own "alert blocks" .UseCallouts() @@ -69,7 +69,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); +builder.Services.AddRazorPages(); builder.Services.AddControllersWithViews(); builder.Services.AddRouting(options => options.LowercaseUrls = true); builder.Services.AddReCaptcha(builder.Configuration.GetSection("ReCaptcha")); @@ -81,15 +81,83 @@ if (builder.Environment.IsProduction()) WebApplication app = builder.Build(); +app.Use(async (ctx, next) => +{ + await next(); + + if (ctx.Response.HasStarted) + { + return; + } + + string? originalPath = ctx.Request.Path.Value; + ctx.Items["originalPath"] = originalPath; + + bool matchedErrorPage = false; + + switch (ctx.Response.StatusCode) + { + case 400: + ctx.Request.Path = "/error/401"; + matchedErrorPage = true; + break; + + case 403: + ctx.Request.Path = "/error/403"; + matchedErrorPage = true; + break; + + case 404: + ctx.Request.Path = "/error/404"; + matchedErrorPage = true; + break; + + case 410: + ctx.Request.Path = "/error/410"; + matchedErrorPage = true; + break; + + case 418: + ctx.Request.Path = "/error/418"; + matchedErrorPage = true; + break; + + case 429: + ctx.Request.Path = "/error/429"; + matchedErrorPage = true; + break; + + case 500: + ctx.Request.Path = "/error/500"; + matchedErrorPage = true; + break; + + case 503: + ctx.Request.Path = "/error/503"; + matchedErrorPage = true; + break; + + case 504: + ctx.Request.Path = "/error/504"; + matchedErrorPage = true; + break; + } + + if (matchedErrorPage) + { + await next(); + } +}); +app.UseStatusCodePagesWithReExecute("/error/{0}"); + if (!app.Environment.IsDevelopment()) { - app.UseExceptionHandler("/Error"); + app.UseExceptionHandler("/error/500"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); -app.UseStatusCodePagesWithRedirects("/error/{0}"); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); diff --git a/OliverBooth/Properties/launchSettings.json b/OliverBooth/Properties/launchSettings.json new file mode 100644 index 0000000..3012f63 --- /dev/null +++ b/OliverBooth/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5000", + "sslPort": 2845 + } + }, + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "workingDirectory": "bin/Debug/net8.0/", + "applicationUrl": "https://localhost:2845", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/OliverBooth/Services/BlogPostService.cs b/OliverBooth/Services/BlogPostService.cs index 38985d3..e844024 100644 --- a/OliverBooth/Services/BlogPostService.cs +++ b/OliverBooth/Services/BlogPostService.cs @@ -2,7 +2,9 @@ using System.Diagnostics.CodeAnalysis; using Humanizer; using Markdig; using Microsoft.EntityFrameworkCore; -using OliverBooth.Data; +using OliverBooth.Common.Data; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Services; using OliverBooth.Data.Blog; namespace OliverBooth.Services; @@ -34,10 +36,12 @@ internal sealed class BlogPostService : IBlogPostService } /// - public int GetBlogPostCount() + public int GetBlogPostCount(Visibility visibility = Visibility.None) { using BlogContext context = _dbContextFactory.CreateDbContext(); - return context.BlogPosts.Count(); + return visibility == Visibility.None + ? context.BlogPosts.Count() + : context.BlogPosts.Count(p => p.Visibility == visibility); } /// @@ -98,6 +102,13 @@ internal sealed class BlogPostService : IBlogPostService .FirstOrDefault(post => post.Published > blogPost.Published); } + /// + public int GetPageCount(int pageSize = 10, Visibility visibility = Visibility.None) + { + float postCount = GetBlogPostCount(visibility); + return (int)MathF.Ceiling(postCount / pageSize); + } + /// public IBlogPost? GetPreviousPost(IBlogPost blogPost) { diff --git a/OliverBooth/Services/BlogUserService.cs b/OliverBooth/Services/BlogUserService.cs index 9b797eb..f7e9137 100644 --- a/OliverBooth/Services/BlogUserService.cs +++ b/OliverBooth/Services/BlogUserService.cs @@ -1,6 +1,8 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Services; using OliverBooth.Data.Blog; namespace OliverBooth.Services; diff --git a/OliverBooth/Services/CodeSnippetService.cs b/OliverBooth/Services/CodeSnippetService.cs index 643f6ea..0c1f6d6 100644 --- a/OliverBooth/Services/CodeSnippetService.cs +++ b/OliverBooth/Services/CodeSnippetService.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; using OliverBooth.Data.Web; namespace OliverBooth.Services; diff --git a/OliverBooth/Services/ContactService.cs b/OliverBooth/Services/ContactService.cs index de2b591..21cb751 100644 --- a/OliverBooth/Services/ContactService.cs +++ b/OliverBooth/Services/ContactService.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; using OliverBooth.Data.Web; namespace OliverBooth.Services; diff --git a/OliverBooth/Services/MastodonService.cs b/OliverBooth/Services/MastodonService.cs index 121a6cc..1e4c9d3 100644 --- a/OliverBooth/Services/MastodonService.cs +++ b/OliverBooth/Services/MastodonService.cs @@ -1,6 +1,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using HtmlAgilityPack; +using OliverBooth.Common.Data.Mastodon; +using OliverBooth.Common.Services; using OliverBooth.Data.Mastodon; namespace OliverBooth.Services; @@ -13,18 +15,20 @@ internal sealed class MastodonService : IMastodonService PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; + private readonly IConfiguration _configuration; private readonly HttpClient _httpClient; - public MastodonService(HttpClient httpClient) + public MastodonService(IConfiguration configuration, HttpClient httpClient) { + _configuration = configuration; _httpClient = httpClient; } /// - public MastodonStatus GetLatestStatus() + public IMastodonStatus GetLatestStatus() { - string token = Environment.GetEnvironmentVariable("MASTODON_TOKEN") ?? string.Empty; - string account = Environment.GetEnvironmentVariable("MASTODON_ACCOUNT") ?? 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"); diff --git a/OliverBooth/Services/ProgrammingLanguageService.cs b/OliverBooth/Services/ProgrammingLanguageService.cs index 3f82bbc..d0f1c4d 100644 --- a/OliverBooth/Services/ProgrammingLanguageService.cs +++ b/OliverBooth/Services/ProgrammingLanguageService.cs @@ -1,21 +1,9 @@ using Microsoft.EntityFrameworkCore; +using OliverBooth.Common.Services; using OliverBooth.Data.Web; namespace OliverBooth.Services; -/// -/// Represents a service which can perform programming language lookup. -/// -public interface IProgrammingLanguageService -{ - /// - /// Returns the human-readable name of a language. - /// - /// The alias of the language. - /// The human-readable name, or if the name could not be found. - string GetLanguageName(string alias); -} - /// internal sealed class ProgrammingLanguageService : IProgrammingLanguageService { diff --git a/OliverBooth/Services/ProjectService.cs b/OliverBooth/Services/ProjectService.cs index 6f75ca5..75eff1b 100644 --- a/OliverBooth/Services/ProjectService.cs +++ b/OliverBooth/Services/ProjectService.cs @@ -2,6 +2,8 @@ using System.Diagnostics.CodeAnalysis; using Humanizer; using Markdig; using Microsoft.EntityFrameworkCore; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; using OliverBooth.Data.Web; namespace OliverBooth.Services; diff --git a/OliverBooth/Services/ReadingListService.cs b/OliverBooth/Services/ReadingListService.cs index 54da5fb..705f056 100644 --- a/OliverBooth/Services/ReadingListService.cs +++ b/OliverBooth/Services/ReadingListService.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; using OliverBooth.Data.Web; namespace OliverBooth.Services; diff --git a/OliverBooth/Services/TemplateService.cs b/OliverBooth/Services/TemplateService.cs index 623a1fd..091da99 100644 --- a/OliverBooth/Services/TemplateService.cs +++ b/OliverBooth/Services/TemplateService.cs @@ -1,10 +1,11 @@ using System.Buffers.Binary; -using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; -using Markdig; using Microsoft.EntityFrameworkCore; +using OliverBooth.Common.Data.Web; using OliverBooth.Data.Web; -using OliverBooth.Formatting; +using OliverBooth.Extensions.Markdig.Markdown.Template; +using OliverBooth.Extensions.Markdig.Services; +using OliverBooth.Extensions.SmartFormat; using OliverBooth.Markdown.Template; using SmartFormat; using SmartFormat.Extensions; diff --git a/OliverBooth/Services/TutorialService.cs b/OliverBooth/Services/TutorialService.cs index 3fd21ed..4a1e387 100644 --- a/OliverBooth/Services/TutorialService.cs +++ b/OliverBooth/Services/TutorialService.cs @@ -3,7 +3,10 @@ using Cysharp.Text; using Humanizer; using Markdig; using Microsoft.EntityFrameworkCore; -using OliverBooth.Data; +using OliverBooth.Common.Data; +using OliverBooth.Common.Data.Blog; +using OliverBooth.Common.Data.Web; +using OliverBooth.Common.Services; using OliverBooth.Data.Blog; using OliverBooth.Data.Web; diff --git a/src/img/error/400-bad-request.png b/src/img/error/400-bad-request.png new file mode 100644 index 0000000..4e09ca8 Binary files /dev/null and b/src/img/error/400-bad-request.png differ diff --git a/src/img/error/403-forbidden.png b/src/img/error/403-forbidden.png new file mode 100644 index 0000000..4abd030 Binary files /dev/null and b/src/img/error/403-forbidden.png differ diff --git a/src/img/error/404-not-found.png b/src/img/error/404-not-found.png new file mode 100644 index 0000000..803530d Binary files /dev/null and b/src/img/error/404-not-found.png differ diff --git a/src/img/error/410-gone.png b/src/img/error/410-gone.png new file mode 100644 index 0000000..69c32db Binary files /dev/null and b/src/img/error/410-gone.png differ diff --git a/src/img/error/418-im-a-teapot.png b/src/img/error/418-im-a-teapot.png new file mode 100644 index 0000000..82292e0 Binary files /dev/null and b/src/img/error/418-im-a-teapot.png differ diff --git a/src/img/error/429-too-many-requests.png b/src/img/error/429-too-many-requests.png new file mode 100644 index 0000000..89bf1fc Binary files /dev/null and b/src/img/error/429-too-many-requests.png differ diff --git a/src/img/error/500-internal-server-error.png b/src/img/error/500-internal-server-error.png new file mode 100644 index 0000000..04039fc Binary files /dev/null and b/src/img/error/500-internal-server-error.png differ diff --git a/src/img/error/503-service-unavailable.png b/src/img/error/503-service-unavailable.png new file mode 100644 index 0000000..6f2b738 Binary files /dev/null and b/src/img/error/503-service-unavailable.png differ diff --git a/src/img/error/504-gateway-timeout.png b/src/img/error/504-gateway-timeout.png new file mode 100644 index 0000000..6f48f98 Binary files /dev/null and b/src/img/error/504-gateway-timeout.png differ diff --git a/src/scss/app.scss b/src/scss/app.scss index df43c57..6960532 100644 --- a/src/scss/app.scss +++ b/src/scss/app.scss @@ -1,4 +1,5 @@ @import "markdown"; +@import "blog"; html, body { background: #121212; @@ -229,19 +230,6 @@ article { } } -.blog-card { - transition: all 0.2s ease-in-out; - - &:hover { - transform: scale(1.05); - } - - article { - background: none; - padding: 0; - } -} - code:not([class*="language-"]) { background: #1e1e1e !important; color: #dcdcda !important; @@ -465,7 +453,13 @@ td.trim-p p:last-child { .mastodon-update-card.card { background-color: desaturate(darken(#6364FF, 50%), 50%); - margin-bottom: 50px; + margin-bottom: 20px; + border-radius: 3px; + border: none; + + .card-body, .card-footer { + border: none; + } p:last-child { margin-bottom: 0; diff --git a/src/scss/blog.scss b/src/scss/blog.scss new file mode 100644 index 0000000..0bfc55f --- /dev/null +++ b/src/scss/blog.scss @@ -0,0 +1,74 @@ +$blog-card-bg: #333333; +$blog-card-gutter: 20px; +$border-radius: 3px; + +div.blog-card { + background: $blog-card-bg; + margin-bottom: $blog-card-gutter; + padding: $blog-card-gutter; + border-radius: $border-radius; + + :last-child { + margin-bottom: 0; + } + + article { + padding: 0; + margin: 0; + } +} + +ul.pagination { + border: none; + + li { + a, span { + border-radius: $border-radius !important; + border: none; + + &:link, &:visited, &:active { + color: #007ec6; + } + } + + &.active a, &.active span { + background: #007ec6; + } + + &:not(.active) a, &:not(.active) span { + background: none; + } + + &:hover { + a { + color: #ffffff !important; + } + } + } +} + +ul.post-tags { + list-style: none; + margin: 0; + padding: 0; + + li.post-tag { + display: inline-block; + margin: 0 5px; + + a { + border-radius: 5px; + border: 1px solid #007ec6; + padding: 5px; + + &:link, &:active, &:visited { + color: #007ec6; + } + + &:hover { + border-color: #ffffff; + color: #ffffff; + } + } + } +} \ No newline at end of file diff --git a/src/ts/API.ts b/src/ts/API.ts deleted file mode 100644 index a1e070d..0000000 --- a/src/ts/API.ts +++ /dev/null @@ -1,44 +0,0 @@ -import BlogPost from "./BlogPost"; -import Author from "./Author"; - -class API { - private static readonly BASE_URL: string = "/api"; - private static readonly BLOG_URL: string = "/blog"; - - static async getBlogPostCount(): Promise { - const response = await API.getResponse(`count`); - return response.count; - } - - static async getBlogPost(id: string): Promise { - const response = await API.getResponse(`post/${id}`); - return new BlogPost(response); - } - - static async getBlogPosts(page: number): Promise { - const response = await API.getResponse(`posts/${page}`); - return response.map(obj => new BlogPost(obj)); - } - - static async getBlogPostsByTag(tag: string, page: number): Promise { - const response = await API.getResponse(`posts/tagged/${tag}/${page}`); - return response.map(obj => new BlogPost(obj)); - } - - static async getAuthor(id: string): Promise { - const response = await API.getResponse(`author/${id}`); - return new Author(response); - } - - private static async getResponse(url: string): Promise { - const response = await fetch(`${API.BASE_URL + API.BLOG_URL}/${url}`); - if (response.status !== 200) { - throw new Error("Invalid response from server"); - } - - const text = await response.text(); - return JSON.parse(text); - } -} - -export default API; \ No newline at end of file diff --git a/src/ts/Author.ts b/src/ts/Author.ts deleted file mode 100644 index 8faf1a5..0000000 --- a/src/ts/Author.ts +++ /dev/null @@ -1,25 +0,0 @@ -class Author { - private readonly _id: string; - private readonly _name: string; - private readonly _avatarUrl: string; - - constructor(json: any) { - this._id = json.id; - this._name = json.name; - this._avatarUrl = json.avatarUrl; - } - - get id(): string { - return this._id; - } - - get name(): string { - return this._name; - } - - get avatarUrl(): string { - return this._avatarUrl; - } -} - -export default Author; \ No newline at end of file diff --git a/src/ts/BlogPost.ts b/src/ts/BlogPost.ts deleted file mode 100644 index 1186964..0000000 --- a/src/ts/BlogPost.ts +++ /dev/null @@ -1,99 +0,0 @@ -import BlogUrl from "./BlogUrl"; - -class BlogPost { - private readonly _id: string; - private readonly _commentsEnabled: boolean; - private readonly _title: string; - private readonly _excerpt: string; - private readonly _content: string; - private readonly _authorId: string; - private readonly _published: Date; - private readonly _updated?: Date; - private readonly _url: BlogUrl; - private readonly _trimmed: boolean; - private readonly _identifier: string; - private readonly _humanizedTimestamp: string; - private readonly _formattedPublishDate: string; - private readonly _formattedUpdateDate: string; - private readonly _tags: string[]; - - constructor(json: any) { - this._id = json.id; - this._commentsEnabled = json.commentsEnabled; - this._title = json.title; - this._excerpt = json.excerpt; - this._content = json.content; - this._authorId = json.author; - this._published = new Date(json.published * 1000); - this._updated = (json.updated && new Date(json.updated * 1000)) || null; - this._url = new BlogUrl(json.url); - this._trimmed = json.trimmed; - this._identifier = json.identifier; - this._humanizedTimestamp = json.humanizedTimestamp; - this._formattedPublishDate = json.formattedPublishDate; - this._formattedUpdateDate = json.formattedUpdateDate; - this._tags = json.tags; - } - - get id(): string { - return this._id; - } - - get commentsEnabled(): boolean { - return this._commentsEnabled; - } - - get title(): string { - return this._title; - } - - get excerpt(): string { - return this._excerpt; - } - - get content(): string { - return this._content; - } - - get authorId(): string { - return this._authorId; - } - - get published(): Date { - return this._published; - } - - get updated(): Date { - return this._updated; - } - - get url(): BlogUrl { - return this._url; - } - - get tags(): string[] { - return this._tags; - } - - get trimmed(): boolean { - return this._trimmed; - } - - get identifier(): string { - return this._identifier; - } - - get humanizedTimestamp(): string { - return this._humanizedTimestamp; - } - - get formattedPublishDate(): string { - return this._formattedPublishDate; - } - - get formattedUpdateDate(): string { - return this._formattedUpdateDate; - } -} - -export default BlogPost; \ No newline at end of file diff --git a/src/ts/BlogUrl.ts b/src/ts/BlogUrl.ts deleted file mode 100644 index 351ad3c..0000000 --- a/src/ts/BlogUrl.ts +++ /dev/null @@ -1,32 +0,0 @@ -class BlogUrl { - private readonly _year: string; - private readonly _month: string; - private readonly _day: string; - private readonly _slug: string; - - constructor(json: any) { - this._year = json.year; - this._month = json.month; - this._day = json.day; - this._slug = json.slug; - } - - - get year(): string { - return this._year; - } - - get month(): string { - return this._month; - } - - get day(): string { - return this._day; - } - - get slug(): string { - return this._slug; - } -} - -export default BlogUrl; \ No newline at end of file diff --git a/src/ts/UI.ts b/src/ts/UI.ts index 3c9d9ed..554eb07 100644 --- a/src/ts/UI.ts +++ b/src/ts/UI.ts @@ -1,5 +1,3 @@ -import BlogPost from "./BlogPost"; -import Author from "./Author"; import TimeUtility from "./TimeUtility"; declare const bootstrap: any; @@ -7,18 +5,6 @@ declare const katex: any; declare const Prism: any; class UI { - public static get blogPost(): HTMLDivElement { - return document.querySelector("article[data-blog-post='true']"); - } - - public static get blogPostContainer(): HTMLDivElement { - return document.querySelector("#all-blog-posts"); - } - - public static get blogPostTemplate(): HTMLDivElement { - return document.querySelector("#blog-post-template"); - } - /** * Creates a