Skip to content

Commit d23a6b6

Browse files
authored
refactor: Moved reading time into BlogPost (#263)
* refactor: Moved reading time into BlogPost * Smaller refactoring * refactor: Remove ref for out * refactoring: Slug generation
1 parent 90e0d8d commit d23a6b6

File tree

6 files changed

+42
-49
lines changed

6 files changed

+42
-49
lines changed

src/LinkDotNet.Blog.Domain/BlogPost.cs

+21-18
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,35 @@ private BlogPost()
3838

3939
public string TagsAsString => Tags is null ? string.Empty : string.Join(", ", Tags);
4040

41+
public int ReadingTimeInMinutes { get; private set; }
42+
4143
public string Slug => GenerateSlug();
4244

4345
private string GenerateSlug()
4446
{
45-
// Remove all accents and make the string lower case.
4647
if (string.IsNullOrWhiteSpace(Title))
48+
{
4749
return Title;
50+
}
4851

49-
Title = Title.Normalize(NormalizationForm.FormD);
50-
var chars = Title
51-
.Where(c => CharUnicodeInfo.GetUnicodeCategory(c)
52-
!= UnicodeCategory.NonSpacingMark)
53-
.ToArray();
54-
55-
Title = new string(chars).Normalize(NormalizationForm.FormC);
56-
57-
var slug = Title.ToLower(CultureInfo.CurrentCulture);
52+
var normalizedTitle = Title.Normalize(NormalizationForm.FormD);
53+
var stringBuilder = new StringBuilder();
5854

59-
// Remove all special characters from the string.
60-
slug = MatchIfSpecialCharactersExist().Replace(slug, "");
55+
foreach (var c in normalizedTitle.Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark))
56+
{
57+
stringBuilder.Append(c);
58+
}
6159

62-
// Remove all additional spaces in favour of just one.
63-
slug= MatchIfAdditionalSpacesExist().Replace(slug," ").Trim();
60+
var cleanTitle = stringBuilder
61+
.ToString()
62+
.Normalize(NormalizationForm.FormC)
63+
.ToLower(CultureInfo.CurrentCulture);
6464

65-
// Replace all spaces with the hyphen.
66-
slug= MatchIfSpaceExist().Replace(slug, "-");
65+
cleanTitle = MatchIfSpecialCharactersExist().Replace(cleanTitle, "");
66+
cleanTitle = MatchIfAdditionalSpacesExist().Replace(cleanTitle, " ");
67+
cleanTitle = MatchIfSpaceExist().Replace(cleanTitle, "-");
6768

68-
return slug;
69+
return cleanTitle.Trim();
6970
}
7071

7172
[GeneratedRegex(
@@ -114,7 +115,8 @@ public static BlogPost Create(
114115
PreviewImageUrl = previewImageUrl,
115116
PreviewImageUrlFallback = previewImageUrlFallback,
116117
IsPublished = isPublished,
117-
Tags = tags?.Select(t => t.Trim()).ToImmutableArray(),
118+
Tags = tags?.Select(t => t.Trim()).ToImmutableArray() ?? ImmutableArray<string>.Empty,
119+
ReadingTimeInMinutes = ReadingTimeCalculator.CalculateReadingTime(content),
118120
};
119121

120122
return blogPost;
@@ -144,5 +146,6 @@ public void Update(BlogPost from)
144146
PreviewImageUrlFallback = from.PreviewImageUrlFallback;
145147
IsPublished = from.IsPublished;
146148
Tags = from.Tags;
149+
ReadingTimeInMinutes = from.ReadingTimeInMinutes;
147150
}
148151
}

src/LinkDotNet.Blog.Web/Features/Services/ReadingTimeCalculator.cs renamed to src/LinkDotNet.Blog.Domain/ReadingTimeCalculator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Text.RegularExpressions;
33

4-
namespace LinkDotNet.Blog.Web.Features.Services;
4+
namespace LinkDotNet.Blog.Domain;
55

66
public static partial class ReadingTimeCalculator
77
{

src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/CreateNewModel.cs

+13-11
Original file line numberDiff line numberDiff line change
@@ -23,65 +23,65 @@ public sealed class CreateNewModel
2323
public string Title
2424
{
2525
get => title;
26-
set => SetProperty(ref title, value);
26+
set => SetProperty(out title, value);
2727
}
2828

2929
[Required]
3030
public string ShortDescription
3131
{
3232
get => shortDescription;
33-
set => SetProperty(ref shortDescription, value);
33+
set => SetProperty(out shortDescription, value);
3434
}
3535

3636
[Required]
3737
public string Content
3838
{
3939
get => content;
40-
set => SetProperty(ref content, value);
40+
set => SetProperty(out content, value);
4141
}
4242

4343
[Required]
4444
[MaxLength(1024)]
4545
public string PreviewImageUrl
4646
{
4747
get => previewImageUrl;
48-
set => SetProperty(ref previewImageUrl, value);
48+
set => SetProperty(out previewImageUrl, value);
4949
}
5050

5151
[Required]
5252
[PublishedWithScheduledDateValidation]
5353
public bool IsPublished
5454
{
5555
get => isPublished;
56-
set => SetProperty(ref isPublished, value);
56+
set => SetProperty(out isPublished, value);
5757
}
5858

5959
[Required]
6060
public bool ShouldUpdateDate
6161
{
6262
get => shouldUpdateDate;
63-
set => SetProperty(ref shouldUpdateDate, value);
63+
set => SetProperty(out shouldUpdateDate, value);
6464
}
6565

6666
[FutureDateValidation]
6767
public DateTime? ScheduledPublishDate
6868
{
6969
get => scheduledPublishDate;
70-
set => SetProperty(ref scheduledPublishDate, value);
70+
set => SetProperty(out scheduledPublishDate, value);
7171
}
7272

7373
public string Tags
7474
{
7575
get => tags;
76-
set => SetProperty(ref tags, value);
76+
set => SetProperty(out tags, value);
7777
}
7878

7979
[MaxLength(256)]
8080
[FallbackUrlValidation]
8181
public string PreviewImageUrlFallback
8282
{
8383
get => previewImageUrlFallback;
84-
set => SetProperty(ref previewImageUrlFallback, value);
84+
set => SetProperty(out previewImageUrlFallback, value);
8585
}
8686

8787
public bool IsDirty { get; private set; }
@@ -108,7 +108,9 @@ public static CreateNewModel FromBlogPost(BlogPost blogPost)
108108

109109
public BlogPost ToBlogPost()
110110
{
111-
var tagList = string.IsNullOrWhiteSpace(Tags) ? ArraySegment<string>.Empty : Tags.Split(",");
111+
var tagList = string.IsNullOrWhiteSpace(Tags)
112+
? Array.Empty<string>()
113+
: Tags.Split(",", StringSplitOptions.RemoveEmptyEntries);
112114
DateTime? updatedDate = ShouldUpdateDate || originalUpdatedDate == default
113115
? null
114116
: originalUpdatedDate;
@@ -127,7 +129,7 @@ public BlogPost ToBlogPost()
127129
return blogPost;
128130
}
129131

130-
private void SetProperty<T>(ref T backingField, T value)
132+
private void SetProperty<T>(out T backingField, T value)
131133
{
132134
backingField = value;
133135
IsDirty = true;

src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor

+2-15
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
@using LinkDotNet.Blog.Web.Features.Services
66
@using Microsoft.Extensions.Caching.Memory
77

8-
@inject IMemoryCache MemoryCache
98
<article>
109
<div class="blog-card @AltCssClass">
1110
<div class="meta">
@@ -24,7 +23,7 @@
2423
<li class="draft">Draft</li>
2524
}
2625
<li class="date me-4"><span>@BlogPost.UpdatedDate.ToString("dd/MM/yyyy")</span></li>
27-
@if (BlogPost.Tags != null && BlogPost.Tags.Any())
26+
@if (BlogPost.Tags.Any())
2827
{
2928
<li class="tags me-4">
3029
<ul>
@@ -35,7 +34,7 @@
3534
</ul>
3635
</li>
3736
}
38-
<li class="read-time me-4">@readingTime min</li>
37+
<li class="read-time me-4">@BlogPost.ReadingTimeInMinutes min</li>
3938
</ul>
4039
</div>
4140
<div class="description">
@@ -50,8 +49,6 @@
5049
</article>
5150

5251
@code {
53-
private int readingTime;
54-
5552
[Parameter]
5653
public BlogPost BlogPost { get; set; }
5754

@@ -83,14 +80,4 @@
8380

8481
return base.SetParametersAsync(ParameterView.Empty);
8582
}
86-
87-
protected override void OnInitialized()
88-
{
89-
var key = "reading-time-" + BlogPost.Id;
90-
readingTime = MemoryCache.GetOrCreate(key, entry =>
91-
{
92-
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
93-
return ReadingTimeCalculator.CalculateReadingTime(BlogPost.Content);
94-
});
95-
}
9683
}

tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ public void ShouldUpdateBlogPost()
1212
{
1313
var blogPostToUpdate = new BlogPostBuilder().Build();
1414
blogPostToUpdate.Id = "random-id";
15-
var blogPost = BlogPost.Create("Title", "Desc", "Content", "Url", true, previewImageUrlFallback: "Url2");
15+
var blogPost = BlogPost.Create("Title", "Desc", "Other Content", "Url", true, previewImageUrlFallback: "Url2");
1616
blogPost.Id = "something else";
1717

1818
blogPostToUpdate.Update(blogPost);
1919

2020
blogPostToUpdate.Title.Should().Be("Title");
2121
blogPostToUpdate.ShortDescription.Should().Be("Desc");
22-
blogPostToUpdate.Content.Should().Be("Content");
22+
blogPostToUpdate.Content.Should().Be("Other Content");
2323
blogPostToUpdate.PreviewImageUrl.Should().Be("Url");
2424
blogPostToUpdate.PreviewImageUrlFallback.Should().Be("Url2");
2525
blogPostToUpdate.IsPublished.Should().BeTrue();
2626
blogPostToUpdate.Tags.Should().BeNullOrEmpty();
2727
blogPostToUpdate.Slug.Should().NotBeNull();
28+
blogPostToUpdate.ReadingTimeInMinutes.Should().Be(1);
2829
}
2930

3031
[Theory]

tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/ReadingTimeCalculatorTests.cs renamed to tests/LinkDotNet.Blog.UnitTests/Domain/ReadingTimeCalculatorTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Linq;
2-
using LinkDotNet.Blog.Web.Features.Services;
2+
using LinkDotNet.Blog.Domain;
33

4-
namespace LinkDotNet.Blog.UnitTests.Web.Features.Services;
4+
namespace LinkDotNet.Blog.UnitTests.Domain;
55

66
public class ReadingTimeCalculatorTests
77
{

0 commit comments

Comments
 (0)