feat: add support for excerpts on blog posts / tutorial articles
This commit is contained in:
parent
cd6bbec1a5
commit
879ff6a295
@ -16,6 +16,9 @@ internal sealed class BlogPost : IBlogPost
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool EnableComments { get; internal set; }
|
public bool EnableComments { get; internal set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string? Excerpt { get; internal set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Guid Id { get; private set; } = Guid.NewGuid();
|
public Guid Id { get; private set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ internal sealed class BlogPostConfiguration : IEntityTypeConfiguration<BlogPost>
|
|||||||
builder.Property(e => e.Updated).IsRequired(false);
|
builder.Property(e => e.Updated).IsRequired(false);
|
||||||
builder.Property(e => e.Title).HasMaxLength(255).IsRequired();
|
builder.Property(e => e.Title).HasMaxLength(255).IsRequired();
|
||||||
builder.Property(e => e.Body).IsRequired();
|
builder.Property(e => e.Body).IsRequired();
|
||||||
|
builder.Property(e => e.Excerpt).HasMaxLength(512).IsRequired(false);
|
||||||
builder.Property(e => e.IsRedirect).IsRequired();
|
builder.Property(e => e.IsRedirect).IsRequired();
|
||||||
builder.Property(e => e.RedirectUrl).HasConversion<UriToStringConverter>().HasMaxLength(255).IsRequired(false);
|
builder.Property(e => e.RedirectUrl).HasConversion<UriToStringConverter>().HasMaxLength(255).IsRequired(false);
|
||||||
builder.Property(e => e.EnableComments).IsRequired();
|
builder.Property(e => e.EnableComments).IsRequired();
|
||||||
|
@ -25,6 +25,12 @@ public interface IBlogPost
|
|||||||
/// </value>
|
/// </value>
|
||||||
bool EnableComments { get; }
|
bool EnableComments { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the excerpt of this post, if it has one.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The excerpt, or <see langword="null" /> if this post has no excerpt.</value>
|
||||||
|
string? Excerpt { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the post.
|
/// Gets the ID of the post.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -16,6 +16,7 @@ internal sealed class TutorialArticleConfiguration : IEntityTypeConfiguration<Tu
|
|||||||
|
|
||||||
builder.Property(e => e.Id).IsRequired();
|
builder.Property(e => e.Id).IsRequired();
|
||||||
builder.Property(e => e.Folder).IsRequired();
|
builder.Property(e => e.Folder).IsRequired();
|
||||||
|
builder.Property(e => e.Excerpt).HasMaxLength(512).IsRequired(false);
|
||||||
builder.Property(e => e.Published).IsRequired();
|
builder.Property(e => e.Published).IsRequired();
|
||||||
builder.Property(e => e.Updated);
|
builder.Property(e => e.Updated);
|
||||||
builder.Property(e => e.Slug).IsRequired();
|
builder.Property(e => e.Slug).IsRequired();
|
||||||
|
@ -11,6 +11,12 @@ public interface ITutorialArticle
|
|||||||
/// <value>The body.</value>
|
/// <value>The body.</value>
|
||||||
string Body { get; }
|
string Body { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the excerpt of this article, if it has one.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The excerpt, or <see langword="null" /> if this article has no excerpt.</value>
|
||||||
|
string? Excerpt { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the folder this article is contained within.
|
/// Gets the ID of the folder this article is contained within.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -8,6 +8,9 @@ internal sealed class TutorialArticle : IEquatable<TutorialArticle>, ITutorialAr
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Body { get; private set; } = string.Empty;
|
public string Body { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string? Excerpt { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int Folder { get; private set; }
|
public int Folder { get; private set; }
|
||||||
|
|
||||||
@ -15,7 +18,7 @@ internal sealed class TutorialArticle : IEquatable<TutorialArticle>, ITutorialAr
|
|||||||
public int Id { get; private set; }
|
public int Id { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int? NextPart { get; }
|
public int? NextPart { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Uri? PreviewImageUrl { get; private set; }
|
public Uri? PreviewImageUrl { get; private set; }
|
||||||
|
@ -90,6 +90,12 @@ internal sealed class BlogPostService : IBlogPostService
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string RenderExcerpt(IBlogPost post, out bool wasTrimmed)
|
public string RenderExcerpt(IBlogPost post, out bool wasTrimmed)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(post.Excerpt))
|
||||||
|
{
|
||||||
|
wasTrimmed = false;
|
||||||
|
return Markdig.Markdown.ToHtml(post.Excerpt, _markdownPipeline);
|
||||||
|
}
|
||||||
|
|
||||||
string body = post.Body;
|
string body = post.Body;
|
||||||
int moreIndex = body.IndexOf("<!--more-->", StringComparison.Ordinal);
|
int moreIndex = body.IndexOf("<!--more-->", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
@ -67,6 +67,17 @@ public interface ITutorialService
|
|||||||
/// <returns>The rendered HTML of the article.</returns>
|
/// <returns>The rendered HTML of the article.</returns>
|
||||||
string RenderArticle(ITutorialArticle article);
|
string RenderArticle(ITutorialArticle article);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the excerpt of the specified article.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="article">The article whose excerpt to render.</param>
|
||||||
|
/// <param name="wasTrimmed">
|
||||||
|
/// When this method returns, contains <see langword="true" /> if the excerpt was trimmed; otherwise,
|
||||||
|
/// <see langword="false" />.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The rendered HTML of the article's excerpt.</returns>
|
||||||
|
string RenderExcerpt(ITutorialArticle article, out bool wasTrimmed);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to find an article by its ID.
|
/// Attempts to find an article by its ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Cysharp.Text;
|
using Cysharp.Text;
|
||||||
|
using Humanizer;
|
||||||
using Markdig;
|
using Markdig;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using OliverBooth.Data;
|
using OliverBooth.Data;
|
||||||
@ -108,6 +109,29 @@ internal sealed class TutorialService : ITutorialService
|
|||||||
return Markdig.Markdown.ToHtml(article.Body, _markdownPipeline);
|
return Markdig.Markdown.ToHtml(article.Body, _markdownPipeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string RenderExcerpt(ITutorialArticle article, out bool wasTrimmed)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(article.Excerpt))
|
||||||
|
{
|
||||||
|
wasTrimmed = false;
|
||||||
|
return Markdig.Markdown.ToHtml(article.Excerpt, _markdownPipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
string body = article.Body;
|
||||||
|
int moreIndex = body.IndexOf("<!--more-->", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
if (moreIndex == -1)
|
||||||
|
{
|
||||||
|
string excerpt = body.Truncate(255, "...");
|
||||||
|
wasTrimmed = body.Length > 255;
|
||||||
|
return Markdig.Markdown.ToHtml(excerpt, _markdownPipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
wasTrimmed = true;
|
||||||
|
return Markdig.Markdown.ToHtml(body[..moreIndex], _markdownPipeline);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool TryGetArticle(int id, [NotNullWhen(true)] out ITutorialArticle? article)
|
public bool TryGetArticle(int id, [NotNullWhen(true)] out ITutorialArticle? article)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user