refactor: separate Markdig extensions from project
Also introduces .Common project to house common references and types
This commit is contained in:
parent
e0037fbff2
commit
6ec4103a3a
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the author of a blog post.
|
/// Represents the author of a blog post.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a blog post.
|
/// Represents a blog post.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a comment that was posted on a legacy comment framework.
|
/// Represents a comment that was posted on a legacy comment framework.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a user which can log in to the blog.
|
/// Represents a user which can log in to the blog.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Mastodon;
|
namespace OliverBooth.Common.Data.Mastodon;
|
||||||
|
|
||||||
public enum AttachmentType
|
public enum AttachmentType
|
||||||
{
|
{
|
31
OliverBooth.Common/Data/Mastodon/IMastodonStatus.cs
Normal file
31
OliverBooth.Common/Data/Mastodon/IMastodonStatus.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
namespace OliverBooth.Common.Data.Mastodon;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a status on Mastodon.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMastodonStatus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content of the status.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The content.</value>
|
||||||
|
string Content { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the date and time at which this status was posted.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The post timestamp.</value>
|
||||||
|
DateTimeOffset CreatedAt { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media attachments for this status.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The media attachments.</value>
|
||||||
|
IReadOnlyList<MediaAttachment> MediaAttachments { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the original URI of the status.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The original URI.</value>
|
||||||
|
Uri OriginalUri { get; }
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Mastodon;
|
namespace OliverBooth.Common.Data.Mastodon;
|
||||||
|
|
||||||
public sealed class MediaAttachment
|
public sealed class MediaAttachment
|
||||||
{
|
{
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data;
|
namespace OliverBooth.Common.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An enumeration of the possible visibilities of a blog post.
|
/// An enumeration of the possible visibilities of a blog post.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the state of a book.
|
/// Represents the state of a book.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an entry in the blacklist.
|
/// Represents an entry in the blacklist.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a book.
|
/// Represents a book.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a code snippet.
|
/// Represents a code snippet.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a programming language.
|
/// Represents a programming language.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a project.
|
/// Represents a project.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a template.
|
/// Represents a template.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a tutorial article.
|
/// Represents a tutorial article.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a folder for tutorial articles.
|
/// Represents a folder for tutorial articles.
|
@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the status of a project.
|
/// Represents the status of a project.
|
16
OliverBooth.Common/OliverBooth.Common.csproj
Normal file
16
OliverBooth.Common/OliverBooth.Common.csproj
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.59"/>
|
||||||
|
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
||||||
|
<PackageReference Include="Markdig" Version="0.36.2"/>
|
||||||
|
<PackageReference Include="ZString" Version="2.5.1"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service for managing blog posts.
|
/// Represents a service for managing blog posts.
|
@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service for managing users.
|
/// Represents a service for managing users.
|
@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service which can fetch multi-language code snippets.
|
/// Represents a service which can fetch multi-language code snippets.
|
@ -1,6 +1,6 @@
|
|||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service for managing contact information.
|
/// Represents a service for managing contact information.
|
@ -1,6 +1,6 @@
|
|||||||
using OliverBooth.Data.Mastodon;
|
using OliverBooth.Common.Data.Mastodon;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
public interface IMastodonService
|
public interface IMastodonService
|
||||||
{
|
{
|
||||||
@ -8,5 +8,5 @@ public interface IMastodonService
|
|||||||
/// Gets the latest status posted to Mastodon.
|
/// Gets the latest status posted to Mastodon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The latest status.</returns>
|
/// <returns>The latest status.</returns>
|
||||||
MastodonStatus GetLatestStatus();
|
IMastodonStatus GetLatestStatus();
|
||||||
}
|
}
|
14
OliverBooth.Common/Services/IProgrammingLanguageService.cs
Normal file
14
OliverBooth.Common/Services/IProgrammingLanguageService.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a service which can perform programming language lookup.
|
||||||
|
/// </summary>
|
||||||
|
public interface IProgrammingLanguageService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the human-readable name of a language.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="alias">The alias of the language.</param>
|
||||||
|
/// <returns>The human-readable name, or <paramref name="alias" /> if the name could not be found.</returns>
|
||||||
|
string GetLanguageName(string alias);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service for interacting with projects.
|
/// Represents a service for interacting with projects.
|
@ -1,6 +1,6 @@
|
|||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service which fetches books from the reading list.
|
/// Represents a service which fetches books from the reading list.
|
@ -1,9 +1,9 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using OliverBooth.Data;
|
using OliverBooth.Common.Data;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Common.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service which can retrieve tutorial articles.
|
/// Represents a service which can retrieve tutorial articles.
|
@ -0,0 +1,43 @@
|
|||||||
|
using Markdig.Helpers;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a callout block.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutBlock : QuoteBlock
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CalloutBlock" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the callout.</param>
|
||||||
|
public CalloutBlock(StringSlice type) : base(null)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this callout is foldable.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><see langword="true" /> if this callout is foldable; otherwise, <see langword="false" />.</value>
|
||||||
|
public bool Foldable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the title of the callout.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The title of the callout.</value>
|
||||||
|
public StringSlice Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the trailing whitespace trivia.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The trailing whitespace trivia.</value>
|
||||||
|
public StringSlice TrailingWhitespaceTrivia { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of the callout.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The type of the callout.</value>
|
||||||
|
public StringSlice Type { get; set; }
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
using Markdig;
|
||||||
|
using Markdig.Parsers.Inlines;
|
||||||
|
using Markdig.Renderers;
|
||||||
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension for adding Obsidian-style callouts to a Markdown pipeline.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutExtension : IMarkdownExtension
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||||
|
{
|
||||||
|
var parser = pipeline.InlineParsers.Find<CalloutInlineParser>();
|
||||||
|
if (parser is null)
|
||||||
|
{
|
||||||
|
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(new CalloutInlineParser());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||||
|
{
|
||||||
|
var blockRenderer = renderer.ObjectRenderers.FindExact<CalloutRenderer>();
|
||||||
|
if (blockRenderer is null)
|
||||||
|
{
|
||||||
|
renderer.ObjectRenderers.InsertBefore<QuoteBlockRenderer>(new CalloutRenderer(pipeline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Cysharp.Text;
|
||||||
|
using Markdig.Helpers;
|
||||||
|
using Markdig.Parsers;
|
||||||
|
using Markdig.Renderers.Html;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An inline parser for Obsidian-style callouts (<c>[!NOTE]</c> etc.)
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutInlineParser : InlineParser
|
||||||
|
{
|
||||||
|
// ugly hack to access internal method
|
||||||
|
private static readonly MethodInfo ReplaceParentContainerMethod =
|
||||||
|
typeof(InlineProcessor).GetMethod("ReplaceParentContainer", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CalloutInlineParser" /> class.
|
||||||
|
/// </summary>
|
||||||
|
public CalloutInlineParser()
|
||||||
|
{
|
||||||
|
OpeningCharacters = ['['];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||||
|
{
|
||||||
|
// We expect the alert to be the first child of a quote block. Example:
|
||||||
|
// > [!NOTE]
|
||||||
|
// > This is a note
|
||||||
|
if (processor.Block is not ParagraphBlock { Parent: QuoteBlock quoteBlock } paragraphBlock ||
|
||||||
|
paragraphBlock.Inline?.FirstChild != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSlice cache = slice;
|
||||||
|
char current = slice.NextChar();
|
||||||
|
|
||||||
|
if (current != '!')
|
||||||
|
{
|
||||||
|
slice = cache;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = slice.NextChar(); // skip !
|
||||||
|
|
||||||
|
int start = slice.Start;
|
||||||
|
int end = start;
|
||||||
|
|
||||||
|
while (current.IsAlphaUpper())
|
||||||
|
{
|
||||||
|
end = slice.Start;
|
||||||
|
current = slice.NextChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != ']' || start == end)
|
||||||
|
{
|
||||||
|
slice = cache;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = new StringSlice(slice.Text, start, end);
|
||||||
|
current = slice.NextChar(); // skip ]
|
||||||
|
start = slice.Start;
|
||||||
|
|
||||||
|
bool fold = false;
|
||||||
|
if (current == '-')
|
||||||
|
{
|
||||||
|
fold = true;
|
||||||
|
current = slice.NextChar(); // skip -
|
||||||
|
start = slice.Start;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadTitle(current, ref slice, out StringSlice title, out end);
|
||||||
|
|
||||||
|
var callout = new CalloutBlock(type)
|
||||||
|
{
|
||||||
|
Foldable = fold,
|
||||||
|
Span = quoteBlock.Span,
|
||||||
|
TrailingWhitespaceTrivia = new StringSlice(slice.Text, start, end),
|
||||||
|
Line = quoteBlock.Line,
|
||||||
|
Column = quoteBlock.Column,
|
||||||
|
Title = title
|
||||||
|
};
|
||||||
|
|
||||||
|
AddAttributes(callout, type);
|
||||||
|
ReplaceQuoteBlock(processor, quoteBlock, callout);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReadTitle(char startChar, ref StringSlice slice, out StringSlice title, out int end)
|
||||||
|
{
|
||||||
|
using Utf16ValueStringBuilder builder = ZString.CreateStringBuilder();
|
||||||
|
|
||||||
|
char current = startChar;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (current is not ('\0' or '\r' or '\n'))
|
||||||
|
{
|
||||||
|
builder.Append(current);
|
||||||
|
current = slice.NextChar();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
end = slice.Start;
|
||||||
|
if (HandleCharacter(ref slice, ref end, ref current))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
title = new StringSlice(builder.ToString(), 0, builder.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HandleCharacter(ref StringSlice slice, ref int end, ref char current)
|
||||||
|
{
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
current = slice.NextChar(); // skip \r
|
||||||
|
|
||||||
|
if (current is not ('\0' or '\n'))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
end = slice.Start;
|
||||||
|
if (current == '\n')
|
||||||
|
{
|
||||||
|
slice.NextChar(); // skip \n
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
slice.NextChar(); // skip \n
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddAttributes(IMarkdownObject callout, StringSlice type)
|
||||||
|
{
|
||||||
|
HtmlAttributes attributes = callout.GetAttributes();
|
||||||
|
attributes.AddClass("callout");
|
||||||
|
attributes.AddProperty("data-callout", type.AsSpan().ToString().ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReplaceQuoteBlock(InlineProcessor processor, QuoteBlock quoteBlock, CalloutBlock callout)
|
||||||
|
{
|
||||||
|
ContainerBlock? parentQuoteBlock = quoteBlock.Parent;
|
||||||
|
if (parentQuoteBlock is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int indexOfQuoteBlock = parentQuoteBlock.IndexOf(quoteBlock);
|
||||||
|
parentQuoteBlock[indexOfQuoteBlock] = callout;
|
||||||
|
|
||||||
|
while (quoteBlock.Count > 0)
|
||||||
|
{
|
||||||
|
var block = quoteBlock[0];
|
||||||
|
quoteBlock.RemoveAt(0);
|
||||||
|
callout.Add(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplaceParentContainerMethod.Invoke(processor, [quoteBlock, callout]);
|
||||||
|
// ReplaceParentContainer(processor, quoteBlock, callout);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
using HtmlAgilityPack;
|
||||||
|
using Humanizer;
|
||||||
|
using Markdig;
|
||||||
|
using Markdig.Renderers;
|
||||||
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an HTML renderer which renders a <see cref="CalloutBlock" />.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutRenderer : HtmlObjectRenderer<CalloutBlock>
|
||||||
|
{
|
||||||
|
private readonly MarkdownPipeline _pipeline;
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, string> CalloutTypes = new()
|
||||||
|
{
|
||||||
|
["NOTE"] = "pencil",
|
||||||
|
["ABSTRACT"] = "clipboard-list",
|
||||||
|
["INFO"] = "info",
|
||||||
|
["TODO"] = "circle-check",
|
||||||
|
["TIP"] = "flame",
|
||||||
|
["IMPORTANT"] = "flame",
|
||||||
|
["SUCCESS"] = "check",
|
||||||
|
["QUESTION"] = "circle-help",
|
||||||
|
["WARNING"] = "triangle-alert",
|
||||||
|
["FAILURE"] = "x",
|
||||||
|
["DANGER"] = "zap",
|
||||||
|
["BUG"] = "bug",
|
||||||
|
["EXAMPLE"] = "list",
|
||||||
|
["CITE"] = "quote",
|
||||||
|
["UPDATE"] = "calendar-check",
|
||||||
|
};
|
||||||
|
|
||||||
|
public CalloutRenderer(MarkdownPipeline pipeline)
|
||||||
|
{
|
||||||
|
_pipeline = pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Write(HtmlRenderer renderer, CalloutBlock block)
|
||||||
|
{
|
||||||
|
renderer.EnsureLine();
|
||||||
|
if (renderer.EnableHtmlForBlock)
|
||||||
|
{
|
||||||
|
RenderAsHtml(renderer, block, _pipeline);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RenderAsText(renderer, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.EnsureLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderAsHtml(HtmlRenderer renderer, CalloutBlock block, MarkdownPipeline pipeline)
|
||||||
|
{
|
||||||
|
string title = block.Title.Text;
|
||||||
|
ReadOnlySpan<char> type = block.Type.AsSpan();
|
||||||
|
Span<char> upperType = stackalloc char[type.Length];
|
||||||
|
type.ToUpperInvariant(upperType);
|
||||||
|
|
||||||
|
if (!CalloutTypes.TryGetValue(upperType.ToString(), out string? lucideClass))
|
||||||
|
{
|
||||||
|
lucideClass = "pencil";
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeString = type.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
|
renderer.Write($"<div class=\"callout\" data-callout=\"{typeString}\"");
|
||||||
|
if (block.Foldable)
|
||||||
|
{
|
||||||
|
renderer.Write(" data-callout-fold=\"true\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.Write('>');
|
||||||
|
renderer.Write("<div class=\"callout-title\"><i data-lucide=\"");
|
||||||
|
renderer.Write(lucideClass);
|
||||||
|
renderer.Write("\"></i> ");
|
||||||
|
|
||||||
|
string calloutTitle = title.Length == 0 ? typeString.Humanize(LetterCasing.Sentence) : title;
|
||||||
|
WriteTitle(renderer, pipeline, calloutTitle);
|
||||||
|
|
||||||
|
if (block.Foldable)
|
||||||
|
{
|
||||||
|
renderer.Write("<span class=\"callout-fold\"><i data-lucide=\"chevron-down\"></i></span>");
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.WriteLine("</div>");
|
||||||
|
renderer.Write("<div class=\"callout-content\">");
|
||||||
|
renderer.WriteChildren(block);
|
||||||
|
renderer.WriteLine("</div>");
|
||||||
|
renderer.WriteLine("</div>");
|
||||||
|
renderer.EnsureLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteTitle(TextRendererBase renderer, MarkdownPipeline pipeline, string calloutTitle)
|
||||||
|
{
|
||||||
|
string html = global::Markdig.Markdown.ToHtml(calloutTitle, pipeline);
|
||||||
|
var document = new HtmlDocument();
|
||||||
|
document.LoadHtml(html);
|
||||||
|
if (document.DocumentNode.FirstChild is { Name: "p" } child)
|
||||||
|
{
|
||||||
|
// ugly hack to remove <p> tag generated by Markdig
|
||||||
|
document.DocumentNode.InnerHtml = child.InnerHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.Save(renderer.Writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderAsText(HtmlRenderer renderer, CalloutBlock block)
|
||||||
|
{
|
||||||
|
string title = block.Title.Text;
|
||||||
|
ReadOnlySpan<char> type = block.Type.AsSpan();
|
||||||
|
renderer.WriteLine(title.Length == 0 ? type.ToString().ToUpperInvariant() : title.ToUpperInvariant());
|
||||||
|
renderer.WriteChildren(block);
|
||||||
|
renderer.EnsureLine();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using Markdig;
|
||||||
|
using OliverBooth.Extensions.Markdig.Markdown.Callout;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="MarkdownPipelineBuilder" />.
|
||||||
|
/// </summary>
|
||||||
|
internal static class MarkdownExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Uses this extension to enable Obsidian-style callouts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pipeline">The pipeline.</param>
|
||||||
|
/// <returns>The modified pipeline.</returns>
|
||||||
|
public static MarkdownPipelineBuilder UseCallouts(this MarkdownPipelineBuilder pipeline)
|
||||||
|
{
|
||||||
|
pipeline.Extensions.AddIfNotAlready<CalloutExtension>();
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
using Markdig;
|
using Markdig;
|
||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Extensions.Markdig.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown extension that adds support for MediaWiki-style templates.
|
/// Represents a Markdown extension that adds support for MediaWiki-style templates.
|
@ -1,6 +1,6 @@
|
|||||||
using Markdig.Syntax.Inlines;
|
using Markdig.Syntax.Inlines;
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown inline element that represents a MediaWiki-style template.
|
/// Represents a Markdown inline element that represents a MediaWiki-style template.
|
@ -2,7 +2,7 @@ using Cysharp.Text;
|
|||||||
using Markdig.Helpers;
|
using Markdig.Helpers;
|
||||||
using Markdig.Parsers;
|
using Markdig.Parsers;
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown inline parser that handles MediaWiki-style templates.
|
/// Represents a Markdown inline parser that handles MediaWiki-style templates.
|
@ -1,8 +1,8 @@
|
|||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
using Markdig.Renderers.Html;
|
using Markdig.Renderers.Html;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Extensions.Markdig.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown object renderer that handles <see cref="TemplateInline" /> elements.
|
/// Represents a Markdown object renderer that handles <see cref="TemplateInline" /> elements.
|
@ -0,0 +1,25 @@
|
|||||||
|
using Markdig;
|
||||||
|
using Markdig.Renderers;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Markdig extension that supports Discord-style timestamps.
|
||||||
|
/// </summary>
|
||||||
|
public class TimestampExtension : IMarkdownExtension
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||||
|
{
|
||||||
|
pipeline.InlineParsers.AddIfNotAlready<TimestampInlineParser>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||||
|
{
|
||||||
|
if (renderer is HtmlRenderer htmlRenderer)
|
||||||
|
{
|
||||||
|
htmlRenderer.ObjectRenderers.AddIfNotAlready<TimestampRenderer>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An enumeration of timestamp formats.
|
||||||
|
/// </summary>
|
||||||
|
public enum TimestampFormat
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Short time format. Example: 12:00
|
||||||
|
/// </summary>
|
||||||
|
ShortTime = 't',
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Long time format. Example: 12:00:00
|
||||||
|
/// </summary>
|
||||||
|
LongTime = 'T',
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short date format. Example: 1/1/2000
|
||||||
|
/// </summary>
|
||||||
|
ShortDate = 'd',
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Long date format. Example: 1 January 2000
|
||||||
|
/// </summary>
|
||||||
|
LongDate = 'D',
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short date/time format. Example: 1 January 2000 at 12:00
|
||||||
|
/// </summary>
|
||||||
|
LongDateShortTime = 'f',
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Long date/time format. Example: Saturday, 1 January 2000 at 12:00
|
||||||
|
/// </summary>
|
||||||
|
LongDateTime = 'F',
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relative date/time format. Example: 1 second ago
|
||||||
|
/// </summary>
|
||||||
|
Relative = 'R',
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using Markdig.Syntax.Inlines;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Markdown inline element that contains a timestamp.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TimestampInline : Inline
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the format.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The format.</value>
|
||||||
|
public TimestampFormat Format { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timestamp.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The timestamp.</value>
|
||||||
|
public DateTimeOffset Timestamp { get; set; }
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
using Markdig.Helpers;
|
||||||
|
using Markdig.Parsers;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Markdown inline parser that matches Discord-style timestamps.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TimestampInlineParser : InlineParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TimestampInlineParser" /> class.
|
||||||
|
/// </summary>
|
||||||
|
public TimestampInlineParser()
|
||||||
|
{
|
||||||
|
OpeningCharacters = new[] { '<' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||||
|
{
|
||||||
|
// Previous char must be a space
|
||||||
|
if (!slice.PeekCharExtra(-1).IsWhiteSpaceOrZero())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<char> span = slice.Text.AsSpan(slice.Start, slice.Length);
|
||||||
|
|
||||||
|
if (!TryConsumeTimestamp(span, out ReadOnlySpan<char> rawTimestamp, out char format))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!long.TryParse(rawTimestamp, out long timestamp))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasFormat = format != '\0';
|
||||||
|
processor.Inline = new TimestampInline
|
||||||
|
{
|
||||||
|
Format = (TimestampFormat)format,
|
||||||
|
Timestamp = DateTimeOffset.FromUnixTimeSeconds(timestamp)
|
||||||
|
};
|
||||||
|
|
||||||
|
int paddingCount = hasFormat ? 6 : 4; // <t:*> or optionally <t:*:*>
|
||||||
|
slice.Start += rawTimestamp.Length + paddingCount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryConsumeTimestamp(ReadOnlySpan<char> source,
|
||||||
|
out ReadOnlySpan<char> timestamp,
|
||||||
|
out char format)
|
||||||
|
{
|
||||||
|
timestamp = default;
|
||||||
|
format = default;
|
||||||
|
|
||||||
|
if (!source.StartsWith("<t:")) return false;
|
||||||
|
timestamp = source[3..];
|
||||||
|
|
||||||
|
if (timestamp.IndexOf('>') == -1)
|
||||||
|
{
|
||||||
|
timestamp = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delimiterIndex = timestamp.IndexOf(':');
|
||||||
|
if (delimiterIndex == 0)
|
||||||
|
{
|
||||||
|
// invalid format <t::*>
|
||||||
|
timestamp = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delimiterIndex == -1)
|
||||||
|
{
|
||||||
|
// no format, default to relative
|
||||||
|
format = 'R';
|
||||||
|
timestamp = timestamp[..^1]; // trim >
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use specified format
|
||||||
|
format = timestamp[^2];
|
||||||
|
timestamp = timestamp[..^3];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Humanizer;
|
||||||
|
using Markdig.Renderers;
|
||||||
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Markdown object renderer that renders <see cref="TimestampInline" /> elements.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TimestampRenderer : HtmlObjectRenderer<TimestampInline>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Write(HtmlRenderer renderer, TimestampInline obj)
|
||||||
|
{
|
||||||
|
DateTimeOffset timestamp = obj.Timestamp;
|
||||||
|
TimestampFormat format = obj.Format;
|
||||||
|
|
||||||
|
renderer.Write("<span class=\"timestamp\" data-timestamp=\"");
|
||||||
|
renderer.Write(timestamp.ToUnixTimeSeconds().ToString());
|
||||||
|
renderer.Write("\" data-format=\"");
|
||||||
|
renderer.Write(((char)format).ToString());
|
||||||
|
renderer.Write("\" title=\"");
|
||||||
|
renderer.WriteEscape(timestamp.ToString("dddd, d MMMM yyyy HH:mm"));
|
||||||
|
renderer.Write("\">");
|
||||||
|
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case TimestampFormat.LongDate:
|
||||||
|
renderer.Write(timestamp.ToString("d MMMM yyyy"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimestampFormat.LongDateShortTime:
|
||||||
|
renderer.Write(timestamp.ToString(@"d MMMM yyyy \a\t HH:mm"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimestampFormat.LongDateTime:
|
||||||
|
renderer.Write(timestamp.ToString(@"dddd, d MMMM yyyy \a\t HH:mm"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimestampFormat.Relative:
|
||||||
|
renderer.Write(timestamp.Humanize());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case var _ when !Enum.IsDefined(format):
|
||||||
|
throw new InvalidEnumArgumentException(nameof(format), (int)format, typeof(TimestampFormat));
|
||||||
|
|
||||||
|
default:
|
||||||
|
renderer.Write(timestamp.ToString(((char)format).ToString()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.Write("</span>");
|
||||||
|
}
|
||||||
|
}
|
38
OliverBooth.Extensions.Markdig/MarkdownPipelineExtensions.cs
Normal file
38
OliverBooth.Extensions.Markdig/MarkdownPipelineExtensions.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using Markdig;
|
||||||
|
using OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
using OliverBooth.Extensions.Markdig.Services;
|
||||||
|
|
||||||
|
namespace OliverBooth.Extensions.Markdig;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="MarkdownPipelineBuilder" />.
|
||||||
|
/// </summary>
|
||||||
|
public static class MarkdownPipelineExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the use of Wiki-style templates in this pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The Markdig markdown pipeline builder.</param>
|
||||||
|
/// <param name="templateService">The template service responsible for fetching and rendering templates.</param>
|
||||||
|
/// <returns>The modified Markdig markdown pipeline builder.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <para><paramref name="builder" /> is <see langword="null" />.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para><paramref name="templateService" /> is <see langword="null" />.</para>
|
||||||
|
/// </exception>
|
||||||
|
public static MarkdownPipelineBuilder UseTemplates(this MarkdownPipelineBuilder builder, ITemplateService templateService)
|
||||||
|
{
|
||||||
|
if (builder is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (templateService is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(templateService));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Use(new TemplateExtension(templateService));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\OliverBooth.Common\OliverBooth.Common.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,8 +1,8 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Markdown.Template;
|
using OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Extensions.Markdig.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service that renders MediaWiki-style templates.
|
/// Represents a service that renders MediaWiki-style templates.
|
@ -13,6 +13,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
global.json = global.json
|
global.json = global.json
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Extensions.Markdig", "OliverBooth.Extensions.Markdig\OliverBooth.Extensions.Markdig.csproj", "{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Common", "OliverBooth.Common\OliverBooth.Common.csproj", "{AD231E0F-FAED-4661-963F-EB22F858E148}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -23,6 +27,14 @@ Global
|
|||||||
{A58A6FA3-480C-400B-822A-3786741BF39C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A58A6FA3-480C-400B-822A-3786741BF39C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AD231E0F-FAED-4661-963F-EB22F858E148}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AD231E0F-FAED-4661-963F-EB22F858E148}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AD231E0F-FAED-4661-963F-EB22F858E148}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AD231E0F-FAED-4661-963F-EB22F858E148}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Controllers.Blog;
|
namespace OliverBooth.Controllers.Blog;
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Blog.Rss;
|
using OliverBooth.Data.Blog.Rss;
|
||||||
using OliverBooth.Services;
|
|
||||||
|
|
||||||
namespace OliverBooth.Controllers.Blog;
|
namespace OliverBooth.Controllers.Blog;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Controllers;
|
namespace OliverBooth.Controllers;
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OliverBooth.Common.Data;
|
||||||
|
using OliverBooth.Common.Data.Blog;
|
||||||
using SmartFormat;
|
using SmartFormat;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using OliverBooth.Common.Data;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog.Configuration;
|
namespace OliverBooth.Data.Blog.Configuration;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Web;
|
using System.Web;
|
||||||
|
using OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Cysharp.Text;
|
using Cysharp.Text;
|
||||||
|
using OliverBooth.Common.Data.Blog;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
|
@ -1,34 +1,24 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using OliverBooth.Common.Data.Mastodon;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Mastodon;
|
namespace OliverBooth.Data.Mastodon;
|
||||||
|
|
||||||
public sealed class MastodonStatus
|
/// <inheritdoc />
|
||||||
|
internal sealed class MastodonStatus : IMastodonStatus
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the content of the status.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The content.</value>
|
|
||||||
[JsonPropertyName("content")]
|
[JsonPropertyName("content")]
|
||||||
public string Content { get; set; } = string.Empty;
|
public string Content { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the date and time at which this status was posted.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The post timestamp.</value>
|
|
||||||
[JsonPropertyName("created_at")]
|
[JsonPropertyName("created_at")]
|
||||||
public DateTimeOffset CreatedAt { get; set; }
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the media attachments for this status.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The media attachments.</value>
|
|
||||||
[JsonPropertyName("media_attachments")]
|
[JsonPropertyName("media_attachments")]
|
||||||
public IReadOnlyList<MediaAttachment> MediaAttachments { get; set; } = ArraySegment<MediaAttachment>.Empty;
|
public IReadOnlyList<MediaAttachment> MediaAttachments { get; set; } = ArraySegment<MediaAttachment>.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the original URI of the status.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The original URI.</value>
|
|
||||||
[JsonPropertyName("url")]
|
[JsonPropertyName("url")]
|
||||||
public Uri OriginalUri { get; set; } = null!;
|
public Uri OriginalUri { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <inheritdoc cref="IBlacklistEntry"/>
|
/// <inheritdoc cref="IBlacklistEntry"/>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using NetBarcode;
|
using NetBarcode;
|
||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using OliverBooth.Common.Data;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using OliverBooth.Common.Data;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <inheritdoc cref="IProgrammingLanguage" />
|
/// <inheritdoc cref="IProgrammingLanguage" />
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OliverBooth.Common.Data;
|
||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
using OliverBooth.Common.Data;
|
||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System.Web;
|
using System.Web;
|
||||||
using Cysharp.Text;
|
using Cysharp.Text;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions;
|
namespace OliverBooth.Extensions;
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Markdig;
|
using Markdig;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
using OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
|
using OliverBooth.Extensions.Markdig.Markdown.Template;
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
|
@ -30,11 +30,8 @@
|
|||||||
<PackageReference Include="Alexinea.Extensions.Configuration.Toml" Version="7.0.0"/>
|
<PackageReference Include="Alexinea.Extensions.Configuration.Toml" Version="7.0.0"/>
|
||||||
<PackageReference Include="AspNetCore.ReCaptcha" Version="1.8.1"/>
|
<PackageReference Include="AspNetCore.ReCaptcha" Version="1.8.1"/>
|
||||||
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
|
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.59"/>
|
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
|
||||||
<PackageReference Include="MailKit" Version="4.4.0"/>
|
<PackageReference Include="MailKit" Version="4.4.0"/>
|
||||||
<PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/>
|
<PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/>
|
||||||
<PackageReference Include="Markdig" Version="0.36.2"/>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3"/>
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/>
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/>
|
||||||
@ -48,7 +45,11 @@
|
|||||||
<PackageReference Include="SmartFormat.NET" Version="3.3.2"/>
|
<PackageReference Include="SmartFormat.NET" Version="3.3.2"/>
|
||||||
<PackageReference Include="X10D" Version="3.3.1"/>
|
<PackageReference Include="X10D" Version="3.3.1"/>
|
||||||
<PackageReference Include="X10D.Hosting" Version="3.3.1"/>
|
<PackageReference Include="X10D.Hosting" Version="3.3.1"/>
|
||||||
<PackageReference Include="ZString" Version="2.5.1"/>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\OliverBooth.Common\OliverBooth.Common.csproj"/>
|
||||||
|
<ProjectReference Include="..\OliverBooth.Extensions.Markdig\OliverBooth.Extensions.Markdig.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
@page "/blog/{year:int}/{month:int}/{day:int}/{slug}"
|
@page "/blog/{year:int}/{month:int}/{day:int}/{slug}"
|
||||||
@using Humanizer
|
@using Humanizer
|
||||||
@using Markdig
|
@using Markdig
|
||||||
@using OliverBooth.Data
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using OliverBooth.Data.Blog
|
@using OliverBooth.Common.Data
|
||||||
@using OliverBooth.Services
|
@using OliverBooth.Common.Data.Blog
|
||||||
|
@using OliverBooth.Common.Services
|
||||||
@inject IBlogPostService BlogPostService
|
@inject IBlogPostService BlogPostService
|
||||||
@inject MarkdownPipeline MarkdownPipeline
|
@inject MarkdownPipeline MarkdownPipeline
|
||||||
@model Article
|
@model Article
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
using BC = BCrypt.Net.BCrypt;
|
using BC = BCrypt.Net.BCrypt;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Blog;
|
namespace OliverBooth.Pages.Blog;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
@page
|
@page
|
||||||
@using Humanizer
|
@using Humanizer
|
||||||
@using OliverBooth.Data.Mastodon
|
@using OliverBooth.Common.Data.Mastodon
|
||||||
@using OliverBooth.Services
|
@using OliverBooth.Common.Services
|
||||||
@model Index
|
@model Index
|
||||||
@inject IMastodonService MastodonService
|
@inject IMastodonService MastodonService
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Blog";
|
ViewData["Title"] = "Blog";
|
||||||
MastodonStatus latestStatus = MastodonService.GetLatestStatus();
|
IMastodonStatus latestStatus = MastodonService.GetLatestStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="card text-center mastodon-update-card">
|
<div class="card text-center mastodon-update-card">
|
||||||
@ -18,16 +18,22 @@
|
|||||||
switch (attachment.Type)
|
switch (attachment.Type)
|
||||||
{
|
{
|
||||||
case AttachmentType.Audio:
|
case AttachmentType.Audio:
|
||||||
<p><audio controls="controls" src="@attachment.Url"></audio></p>
|
<p>
|
||||||
|
<audio controls="controls" src="@attachment.Url"></audio>
|
||||||
|
</p>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AttachmentType.Video:
|
case AttachmentType.Video:
|
||||||
<p><video controls="controls" class="figure-img img-fluid" src="@attachment.Url"></video></p>
|
<p>
|
||||||
|
<video controls="controls" class="figure-img img-fluid" src="@attachment.Url"></video>
|
||||||
|
</p>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AttachmentType.Image:
|
case AttachmentType.Image:
|
||||||
case AttachmentType.GifV:
|
case AttachmentType.GifV:
|
||||||
<p><img class="figure-img img-fluid" src="@attachment.Url"></p>
|
<p>
|
||||||
|
<img class="figure-img img-fluid" src="@attachment.Url">
|
||||||
|
</p>
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Blog;
|
namespace OliverBooth.Pages.Blog;
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using Cysharp.Text;
|
using Cysharp.Text;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Common.Data.Blog;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Blog;
|
namespace OliverBooth.Pages.Blog;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@page
|
@page
|
||||||
@using OliverBooth.Data.Web
|
@using OliverBooth.Common.Data.Web
|
||||||
@model OliverBooth.Pages.Books
|
@model OliverBooth.Pages.Books
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Reading List";
|
ViewData["Title"] = "Reading List";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages;
|
namespace OliverBooth.Pages;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
@page
|
@page
|
||||||
@using OliverBooth.Data.Web
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using OliverBooth.Services
|
@using OliverBooth.Common.Data.Web
|
||||||
|
@using OliverBooth.Common.Services
|
||||||
@inject IContactService ContactService
|
@inject IContactService ContactService
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Blacklist";
|
ViewData["Title"] = "Blacklist";
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@page
|
@page
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Contact";
|
ViewData["Title"] = "Contact";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@page
|
@page
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@model OliverBooth.Pages.Contact.Result
|
@model OliverBooth.Pages.Contact.Result
|
||||||
|
|
||||||
@{
|
@{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@page
|
@page
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<div class="row align-items-center mb-3">
|
<div class="row align-items-center mb-3">
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@page "/privacy/five-oclock-somewhere"
|
@page "/privacy/five-oclock-somewhere"
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "It's 5 O'Clock Somewhere Privacy Policy";
|
ViewData["Title"] = "It's 5 O'Clock Somewhere Privacy Policy";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@page "/privacy/google-play"
|
@page "/privacy/google-play"
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Google Play Privacy Policy";
|
ViewData["Title"] = "Google Play Privacy Policy";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@page
|
@page
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Privacy Policy";
|
ViewData["Title"] = "Privacy Policy";
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
@page
|
@page
|
||||||
@using OliverBooth.Data.Web
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using OliverBooth.Services
|
@using OliverBooth.Common.Data.Web
|
||||||
|
@using OliverBooth.Common.Services
|
||||||
@inject IProjectService ProjectService
|
@inject IProjectService ProjectService
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Projects";
|
ViewData["Title"] = "Projects";
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
@page "/project/{slug}"
|
@page "/project/{slug}"
|
||||||
@using Markdig
|
@using Markdig
|
||||||
@using OliverBooth.Data.Web
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using OliverBooth.Services
|
@using OliverBooth.Common.Data.Web
|
||||||
|
@using OliverBooth.Common.Services
|
||||||
@model Project
|
@model Project
|
||||||
@inject IProjectService ProjectService
|
@inject IProjectService ProjectService
|
||||||
@inject MarkdownPipeline MarkdownPipeline
|
@inject MarkdownPipeline MarkdownPipeline
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Projects;
|
namespace OliverBooth.Pages.Projects;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
@using OliverBooth.Data.Blog
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using OliverBooth.Data.Web
|
@using OliverBooth.Common.Data.Blog
|
||||||
|
@using OliverBooth.Common.Data.Web
|
||||||
|
@using OliverBooth.Common.Services
|
||||||
@using OliverBooth.Extensions
|
@using OliverBooth.Extensions
|
||||||
@using OliverBooth.Services
|
|
||||||
@inject IBlogPostService BlogPostService
|
@inject IBlogPostService BlogPostService
|
||||||
@inject ITutorialService TutorialService
|
@inject ITutorialService TutorialService
|
||||||
@{
|
@{
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-bs-theme="dark">
|
<html lang="en" data-bs-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
@using Humanizer
|
@using Humanizer
|
||||||
@using Markdig
|
@using Markdig
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using OliverBooth.Data
|
@using OliverBooth.Common.Data
|
||||||
@using OliverBooth.Data.Blog
|
@using OliverBooth.Common.Data.Blog
|
||||||
@using OliverBooth.Data.Web
|
@using OliverBooth.Common.Data.Web
|
||||||
@using OliverBooth.Services
|
@using OliverBooth.Common.Services
|
||||||
@inject ITutorialService TutorialService
|
@inject ITutorialService TutorialService
|
||||||
@inject MarkdownPipeline MarkdownPipeline
|
@inject MarkdownPipeline MarkdownPipeline
|
||||||
@model Article
|
@model Article
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Tutorials;
|
namespace OliverBooth.Pages.Tutorials;
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
@page "/tutorials/{**slug}"
|
@page "/tutorials/{**slug}"
|
||||||
@using System.Text
|
@using System.Text
|
||||||
@using OliverBooth.Data
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using OliverBooth.Data.Web
|
@using OliverBooth.Common.Data
|
||||||
@using OliverBooth.Services
|
@using OliverBooth.Common.Data.Web
|
||||||
|
@using OliverBooth.Common.Services
|
||||||
@model Index
|
@model Index
|
||||||
@inject ITutorialService TutorialService
|
@inject ITutorialService TutorialService
|
||||||
@{
|
@{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Common.Data.Web;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Common.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Tutorials;
|
namespace OliverBooth.Pages.Tutorials;
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using AspNetCore.ReCaptcha;
|
using AspNetCore.ReCaptcha;
|
||||||
using Markdig;
|
using Markdig;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Extensions;
|
using OliverBooth.Extensions;
|
||||||
|
using OliverBooth.Extensions.Markdig;
|
||||||
|
using OliverBooth.Extensions.Markdig.Services;
|
||||||
using OliverBooth.Markdown;
|
using OliverBooth.Markdown;
|
||||||
using OliverBooth.Markdown.Callout;
|
|
||||||
using OliverBooth.Markdown.Template;
|
|
||||||
using OliverBooth.Markdown.Timestamp;
|
using OliverBooth.Markdown.Timestamp;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@ -25,7 +26,7 @@ builder.Logging.AddSerilog();
|
|||||||
|
|
||||||
builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder()
|
builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder()
|
||||||
.Use<TimestampExtension>()
|
.Use<TimestampExtension>()
|
||||||
.Use(new TemplateExtension(provider.GetRequiredService<ITemplateService>()))
|
.UseTemplates(provider.GetRequiredService<ITemplateService>())
|
||||||
|
|
||||||
// we have our own "alert blocks"
|
// we have our own "alert blocks"
|
||||||
.UseCallouts()
|
.UseCallouts()
|
||||||
|
@ -2,7 +2,9 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Markdig;
|
using Markdig;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using OliverBooth.Data;
|
using OliverBooth.Common.Data;
|
||||||
|
using OliverBooth.Common.Data.Blog;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Services;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using OliverBooth.Common.Data.Blog;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Services;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Services;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using OliverBooth.Common.Data.Web;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Services;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
|
using OliverBooth.Common.Data.Mastodon;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Mastodon;
|
using OliverBooth.Data.Mastodon;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Services;
|
||||||
@ -21,7 +23,7 @@ internal sealed class MastodonService : IMastodonService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public MastodonStatus GetLatestStatus()
|
public IMastodonStatus GetLatestStatus()
|
||||||
{
|
{
|
||||||
string token = Environment.GetEnvironmentVariable("MASTODON_TOKEN") ?? string.Empty;
|
string token = Environment.GetEnvironmentVariable("MASTODON_TOKEN") ?? string.Empty;
|
||||||
string account = Environment.GetEnvironmentVariable("MASTODON_ACCOUNT") ?? string.Empty;
|
string account = Environment.GetEnvironmentVariable("MASTODON_ACCOUNT") ?? string.Empty;
|
||||||
|
@ -1,21 +1,9 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using OliverBooth.Common.Services;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
|
|
||||||
namespace OliverBooth.Services;
|
namespace OliverBooth.Services;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a service which can perform programming language lookup.
|
|
||||||
/// </summary>
|
|
||||||
public interface IProgrammingLanguageService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the human-readable name of a language.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="alias">The alias of the language.</param>
|
|
||||||
/// <returns>The human-readable name, or <paramref name="alias" /> if the name could not be found.</returns>
|
|
||||||
string GetLanguageName(string alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
internal sealed class ProgrammingLanguageService : IProgrammingLanguageService
|
internal sealed class ProgrammingLanguageService : IProgrammingLanguageService
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user