From 0f7fec826240eda7487314037e8c683bab3b2e63 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Sun, 3 Dec 2023 20:15:39 +0330 Subject: [PATCH 01/18] add BlogPostIdGenerator to generate shorter id --- .../Sql/Mapping/BlogPostConfiguration.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs b/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs index 05928931..9630f922 100644 --- a/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs +++ b/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs @@ -1,6 +1,9 @@ using LinkDotNet.Blog.Domain; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.ValueGeneration; +using System; namespace LinkDotNet.Blog.Infrastructure.Persistence.Sql.Mapping; @@ -11,7 +14,8 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(c => c.Id); builder.Property(c => c.Id) .IsUnicode(false) - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasValueGenerator(); builder.Property(x => x.Title).HasMaxLength(256).IsRequired(); builder.Property(x => x.PreviewImageUrl).HasMaxLength(1024).IsRequired(); builder.Property(x => x.PreviewImageUrlFallback).HasMaxLength(1024); @@ -26,3 +30,14 @@ public void Configure(EntityTypeBuilder builder) .IsDescending(false, true); } } + + +internal sealed class BlogPostIdGenerator : ValueGenerator +{ + public override bool GeneratesTemporaryValues => false; + + public override string Next(EntityEntry entry) + { + return Guid.NewGuid().ToString("N").Substring(0, 15); + } +} From ce66b7aa3fd6912264bdb554ded415d1ea67ab21 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Sun, 3 Dec 2023 20:33:16 +0330 Subject: [PATCH 02/18] add SeoFriendlyUrl prop to blog posts --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 16 +++++++++++++++- .../Features/Archive/ArchivePage.razor | 8 ++++---- .../Features/Components/ShortBlogPost.razor | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index 6930fe3c..1287cfb1 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -1,6 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; using System.Linq; namespace LinkDotNet.Blog.Domain; @@ -35,6 +37,18 @@ private BlogPost() public string TagsAsString => Tags is null ? string.Empty : string.Join(", ", Tags); + [NotMapped] + public string SeoFriendlyUrl => GenerateSeoFriendlyUrl(); + + private string GenerateSeoFriendlyUrl() + { + string seoFriendlyTitle = Title + .Replace(' ', '-') + .ToLower(CultureInfo.CurrentCulture); + + return $"{Id}/{seoFriendlyTitle}"; + } + public static BlogPost Create( string title, string shortDescription, diff --git a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor index b6315af9..a88c131b 100644 --- a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor @@ -21,12 +21,12 @@ } } - + @code { private IReadOnlyCollection> blogPostsPerYear; @@ -35,7 +35,7 @@ protected override async Task OnInitializedAsync() { var blogPosts = await Repository.GetAllByProjectionAsync( - p => new BlogPostPerYear(p.Id, p.Title, p.UpdatedDate), + p => new BlogPostPerYear(p.Id, p.SeoFriendlyUrl, p.Title, p.UpdatedDate), p => p.IsPublished); blogPostCount = blogPosts.Count; blogPostsPerYear = blogPosts @@ -44,5 +44,5 @@ .ToImmutableArray(); } - private sealed record BlogPostPerYear(string Id, string Title, DateTime UpdatedDate); + private sealed record BlogPostPerYear(string Id, string SeoFriendlyUrl, string Title, DateTime UpdatedDate); } diff --git a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor index ceaa3efe..386c99eb 100644 --- a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor +++ b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor @@ -1,4 +1,4 @@ -@using System.Net +@using System.Net @using System.Text.RegularExpressions @using System.Web @using LinkDotNet.Blog.Domain @@ -43,7 +43,7 @@

@MarkdownConverter.ToMarkupString(BlogPost.ShortDescription)

- Read the whole article + Read the whole article

From 88e6f962fcc1f2ddaf4fd4c12d5bc7255b959ee5 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Sun, 3 Dec 2023 20:47:46 +0330 Subject: [PATCH 03/18] fix routing parameter --- .../Features/ShowBlogPost/ShowBlogPostPage.razor | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor index a666f509..c6729e91 100644 --- a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor @@ -1,4 +1,4 @@ -@page "/blogPost/{blogPostId}" +@page "/blogPost/{blogPostId}/{seoFriendlyTitle}" @using Markdig @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure.Persistence @@ -72,6 +72,9 @@ else [Parameter] public string BlogPostId { get; set; } + [Parameter] + public string SeoFriendlyTitle { get; set; } + private string OgDataImage => BlogPost.PreviewImageUrlFallback ?? BlogPost.PreviewImageUrl; private BlogPost BlogPost { get; set; } From 2f0c18dc624a6084ccead1470f8ebd016fa1668a Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Sun, 3 Dec 2023 20:55:10 +0330 Subject: [PATCH 04/18] fix RSS links --- src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs index 3dc80f1d..661d405a 100644 --- a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs +++ b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs @@ -67,7 +67,7 @@ private static XmlWriterSettings CreateXmlWriterSettings() private static SyndicationItem CreateSyndicationItemFromBlogPost(string url, BlogPostRssInfo blogPost) { - var blogPostUrl = url + $"/blogPost/{blogPost.Id}"; + var blogPostUrl = url + $"/blogPost/{blogPost.SeoFriendlyUrl}"; var shortDescription = MarkdownConverter.ToPlainString(blogPost.ShortDescription); var item = new SyndicationItem( blogPost.Title, @@ -96,7 +96,7 @@ private static void AddCategories(Collection categories, Bl private async Task> GetBlogPostItems(string url) { var blogPosts = await blogPostRepository.GetAllByProjectionAsync( - s => new BlogPostRssInfo(s.Id, s.Title, s.ShortDescription, s.UpdatedDate, s.PreviewImageUrl, s.Tags), + s => new BlogPostRssInfo(s.Id, s.Title, s.ShortDescription, s.UpdatedDate, s.PreviewImageUrl, s.Tags, s.SeoFriendlyUrl), f => f.IsPublished, orderBy: post => post.UpdatedDate); return blogPosts.Select(bp => CreateSyndicationItemFromBlogPost(url, bp)); @@ -108,5 +108,6 @@ private sealed record BlogPostRssInfo( string ShortDescription, DateTime UpdatedDate, string PreviewImageUrl, - IEnumerable Tags); + IEnumerable Tags, + string SeoFriendlyUrl); } From c926eb0ae6fa5d9725b67fd192d51634e119b7e1 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Sun, 3 Dec 2023 21:07:28 +0330 Subject: [PATCH 05/18] refactor SeoFriendlyUrl property name --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 8 ++++---- src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs | 6 +++--- .../Features/Archive/ArchivePage.razor | 6 +++--- .../Features/Components/ShortBlogPost.razor | 2 +- .../Features/ShowBlogPost/ShowBlogPostPage.razor | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index 1287cfb1..6233f281 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -38,15 +38,15 @@ private BlogPost() public string TagsAsString => Tags is null ? string.Empty : string.Join(", ", Tags); [NotMapped] - public string SeoFriendlyUrl => GenerateSeoFriendlyUrl(); + public string SearchEngineFriendlyUrl => GenerateSearchEngineFriendlyUrl(); - private string GenerateSeoFriendlyUrl() + private string GenerateSearchEngineFriendlyUrl() { - string seoFriendlyTitle = Title + string SearchEngineFriendlyTitle = Title .Replace(' ', '-') .ToLower(CultureInfo.CurrentCulture); - return $"{Id}/{seoFriendlyTitle}"; + return $"{Id}/{SearchEngineFriendlyTitle}"; } public static BlogPost Create( diff --git a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs index 661d405a..b2427073 100644 --- a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs +++ b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs @@ -67,7 +67,7 @@ private static XmlWriterSettings CreateXmlWriterSettings() private static SyndicationItem CreateSyndicationItemFromBlogPost(string url, BlogPostRssInfo blogPost) { - var blogPostUrl = url + $"/blogPost/{blogPost.SeoFriendlyUrl}"; + var blogPostUrl = url + $"/blogPost/{blogPost.SearchEngineFriendlyUrl}"; var shortDescription = MarkdownConverter.ToPlainString(blogPost.ShortDescription); var item = new SyndicationItem( blogPost.Title, @@ -96,7 +96,7 @@ private static void AddCategories(Collection categories, Bl private async Task> GetBlogPostItems(string url) { var blogPosts = await blogPostRepository.GetAllByProjectionAsync( - s => new BlogPostRssInfo(s.Id, s.Title, s.ShortDescription, s.UpdatedDate, s.PreviewImageUrl, s.Tags, s.SeoFriendlyUrl), + s => new BlogPostRssInfo(s.Id, s.Title, s.ShortDescription, s.UpdatedDate, s.PreviewImageUrl, s.Tags, s.SearchEngineFriendlyUrl), f => f.IsPublished, orderBy: post => post.UpdatedDate); return blogPosts.Select(bp => CreateSyndicationItemFromBlogPost(url, bp)); @@ -109,5 +109,5 @@ private sealed record BlogPostRssInfo( DateTime UpdatedDate, string PreviewImageUrl, IEnumerable Tags, - string SeoFriendlyUrl); + string SearchEngineFriendlyUrl); } diff --git a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor index a88c131b..2720a143 100644 --- a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor @@ -21,7 +21,7 @@ } @@ -35,7 +35,7 @@ protected override async Task OnInitializedAsync() { var blogPosts = await Repository.GetAllByProjectionAsync( - p => new BlogPostPerYear(p.Id, p.SeoFriendlyUrl, p.Title, p.UpdatedDate), + p => new BlogPostPerYear(p.Id, p.SearchEngineFriendlyUrl, p.Title, p.UpdatedDate), p => p.IsPublished); blogPostCount = blogPosts.Count; blogPostsPerYear = blogPosts @@ -44,5 +44,5 @@ .ToImmutableArray(); } - private sealed record BlogPostPerYear(string Id, string SeoFriendlyUrl, string Title, DateTime UpdatedDate); + private sealed record BlogPostPerYear(string Id, string SearchEngineFriendlyUrl, string Title, DateTime UpdatedDate); } diff --git a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor index 386c99eb..53d97fc7 100644 --- a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor +++ b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor @@ -43,7 +43,7 @@

@MarkdownConverter.ToMarkupString(BlogPost.ShortDescription)

- Read the whole article + Read the whole article

diff --git a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor index c6729e91..e52f0b15 100644 --- a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor @@ -1,4 +1,4 @@ -@page "/blogPost/{blogPostId}/{seoFriendlyTitle}" +@page "/blogPost/{blogPostId}/{searchEngineFriendlyTitle}" @using Markdig @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure.Persistence @@ -73,7 +73,7 @@ else public string BlogPostId { get; set; } [Parameter] - public string SeoFriendlyTitle { get; set; } + public string SearchEngineFriendlyTitle { get; set; } private string OgDataImage => BlogPost.PreviewImageUrlFallback ?? BlogPost.PreviewImageUrl; From f43a1bd719cc87b40c21afc34d9aefa850cf6340 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Mon, 4 Dec 2023 21:49:16 +0330 Subject: [PATCH 06/18] unused NotMapped removed --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index 6233f281..5534d8f1 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -37,7 +37,6 @@ private BlogPost() public string TagsAsString => Tags is null ? string.Empty : string.Join(", ", Tags); - [NotMapped] public string SearchEngineFriendlyUrl => GenerateSearchEngineFriendlyUrl(); private string GenerateSearchEngineFriendlyUrl() From 737abd8703b9e387a460ea46e0f529bbea1a679d Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Mon, 4 Dec 2023 21:51:26 +0330 Subject: [PATCH 07/18] URL-Friendly removed from the RSS part --- src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs index b2427073..3dc80f1d 100644 --- a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs +++ b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs @@ -67,7 +67,7 @@ private static XmlWriterSettings CreateXmlWriterSettings() private static SyndicationItem CreateSyndicationItemFromBlogPost(string url, BlogPostRssInfo blogPost) { - var blogPostUrl = url + $"/blogPost/{blogPost.SearchEngineFriendlyUrl}"; + var blogPostUrl = url + $"/blogPost/{blogPost.Id}"; var shortDescription = MarkdownConverter.ToPlainString(blogPost.ShortDescription); var item = new SyndicationItem( blogPost.Title, @@ -96,7 +96,7 @@ private static void AddCategories(Collection categories, Bl private async Task> GetBlogPostItems(string url) { var blogPosts = await blogPostRepository.GetAllByProjectionAsync( - s => new BlogPostRssInfo(s.Id, s.Title, s.ShortDescription, s.UpdatedDate, s.PreviewImageUrl, s.Tags, s.SearchEngineFriendlyUrl), + s => new BlogPostRssInfo(s.Id, s.Title, s.ShortDescription, s.UpdatedDate, s.PreviewImageUrl, s.Tags), f => f.IsPublished, orderBy: post => post.UpdatedDate); return blogPosts.Select(bp => CreateSyndicationItemFromBlogPost(url, bp)); @@ -108,6 +108,5 @@ private sealed record BlogPostRssInfo( string ShortDescription, DateTime UpdatedDate, string PreviewImageUrl, - IEnumerable Tags, - string SearchEngineFriendlyUrl); + IEnumerable Tags); } From 6afa75e271d3f1a7166c0f13489acf28cf0ed9a9 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Mon, 4 Dec 2023 22:17:30 +0330 Subject: [PATCH 08/18] BlogPostId value generator removed --- .../Sql/Mapping/BlogPostConfiguration.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs b/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs index 9630f922..01919252 100644 --- a/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs +++ b/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/Mapping/BlogPostConfiguration.cs @@ -1,9 +1,6 @@ -using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Domain; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.ValueGeneration; -using System; namespace LinkDotNet.Blog.Infrastructure.Persistence.Sql.Mapping; @@ -14,8 +11,7 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(c => c.Id); builder.Property(c => c.Id) .IsUnicode(false) - .ValueGeneratedOnAdd() - .HasValueGenerator(); + .ValueGeneratedOnAdd(); builder.Property(x => x.Title).HasMaxLength(256).IsRequired(); builder.Property(x => x.PreviewImageUrl).HasMaxLength(1024).IsRequired(); builder.Property(x => x.PreviewImageUrlFallback).HasMaxLength(1024); @@ -30,14 +26,3 @@ public void Configure(EntityTypeBuilder builder) .IsDescending(false, true); } } - - -internal sealed class BlogPostIdGenerator : ValueGenerator -{ - public override bool GeneratesTemporaryValues => false; - - public override string Next(EntityEntry entry) - { - return Guid.NewGuid().ToString("N").Substring(0, 15); - } -} From 8dcdeb5d1b293e3ee1be2cfa1f63671862b95566 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Mon, 4 Dec 2023 22:43:17 +0330 Subject: [PATCH 09/18] GenerateSearchEngineFriendlyUrl fixed --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 27 ++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index 5534d8f1..d775b68f 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.ComponentModel.DataAnnotations.Schema; using System.Globalization; using System.Linq; +using System.Text; +using System.Text.RegularExpressions; namespace LinkDotNet.Blog.Domain; @@ -41,9 +42,27 @@ private BlogPost() private string GenerateSearchEngineFriendlyUrl() { - string SearchEngineFriendlyTitle = Title - .Replace(' ', '-') - .ToLower(CultureInfo.CurrentCulture); + // Remove all accents and make the string lower case. + if (string.IsNullOrWhiteSpace(Title)) + return Title; + + Title = Title.Normalize(NormalizationForm.FormD); + char[] chars = Title + .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) + != UnicodeCategory.NonSpacingMark).ToArray(); + + Title = new string(chars).Normalize(NormalizationForm.FormC); + + string SearchEngineFriendlyTitle = Title.ToLower(CultureInfo.CurrentCulture); + + // Remove all special characters from the string. + SearchEngineFriendlyTitle = Regex.Replace(SearchEngineFriendlyTitle, @"[^A-Za-z0-9\s-]", ""); + + // Remove all additional spaces in favour of just one. + SearchEngineFriendlyTitle = Regex.Replace(SearchEngineFriendlyTitle, @"\s+", " ").Trim(); + + // Replace all spaces with the hyphen. + SearchEngineFriendlyTitle = Regex.Replace(SearchEngineFriendlyTitle, @"\s", "-"); return $"{Id}/{SearchEngineFriendlyTitle}"; } From 514b8712e06145c3680558b43552f144b06bdf40 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Mon, 4 Dec 2023 22:58:20 +0330 Subject: [PATCH 10/18] make searchEngineFriendlyTitle optional --- .../Features/ShowBlogPost/ShowBlogPostPage.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor index e52f0b15..1ca373a6 100644 --- a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor @@ -1,4 +1,4 @@ -@page "/blogPost/{blogPostId}/{searchEngineFriendlyTitle}" +@page "/blogPost/{blogPostId}/{searchEngineFriendlyTitle?}" @using Markdig @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure.Persistence From 46c98b9811743a519c47134a5428662df0969ebf Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Tue, 5 Dec 2023 20:36:25 +0330 Subject: [PATCH 11/18] renaming SearchEngineFriendlyUrl to Slug --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 4 ++-- src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor | 6 +++--- .../Features/Components/ShortBlogPost.razor | 2 +- .../Features/ShowBlogPost/ShowBlogPostPage.razor | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index d775b68f..ed19c1b6 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -38,9 +38,9 @@ private BlogPost() public string TagsAsString => Tags is null ? string.Empty : string.Join(", ", Tags); - public string SearchEngineFriendlyUrl => GenerateSearchEngineFriendlyUrl(); + public string Slug => GenerateSlug(); - private string GenerateSearchEngineFriendlyUrl() + private string GenerateSlug() { // Remove all accents and make the string lower case. if (string.IsNullOrWhiteSpace(Title)) diff --git a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor index 2720a143..e17c7802 100644 --- a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor @@ -21,7 +21,7 @@ } @@ -35,7 +35,7 @@ protected override async Task OnInitializedAsync() { var blogPosts = await Repository.GetAllByProjectionAsync( - p => new BlogPostPerYear(p.Id, p.SearchEngineFriendlyUrl, p.Title, p.UpdatedDate), + p => new BlogPostPerYear(p.Id, p.Slug, p.Title, p.UpdatedDate), p => p.IsPublished); blogPostCount = blogPosts.Count; blogPostsPerYear = blogPosts @@ -44,5 +44,5 @@ .ToImmutableArray(); } - private sealed record BlogPostPerYear(string Id, string SearchEngineFriendlyUrl, string Title, DateTime UpdatedDate); + private sealed record BlogPostPerYear(string Id, string Slug, string Title, DateTime UpdatedDate); } diff --git a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor index 53d97fc7..a84ffac7 100644 --- a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor +++ b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor @@ -43,7 +43,7 @@

@MarkdownConverter.ToMarkupString(BlogPost.ShortDescription)

- Read the whole article + Read the whole article

diff --git a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor index 1ca373a6..2fe29109 100644 --- a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor @@ -1,4 +1,4 @@ -@page "/blogPost/{blogPostId}/{searchEngineFriendlyTitle?}" +@page "/blogPost/{blogPostId}/{slug?}" @using Markdig @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure.Persistence @@ -73,7 +73,7 @@ else public string BlogPostId { get; set; } [Parameter] - public string SearchEngineFriendlyTitle { get; set; } + public string Slug { get; set; } private string OgDataImage => BlogPost.PreviewImageUrlFallback ?? BlogPost.PreviewImageUrl; From deffa8b46e4335821dc332e37754c35b4e0e9ba7 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Thu, 7 Dec 2023 11:38:00 +0330 Subject: [PATCH 12/18] small refactoring in coding style --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index ed19c1b6..ceb7a1a4 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -47,13 +47,14 @@ private string GenerateSlug() return Title; Title = Title.Normalize(NormalizationForm.FormD); - char[] chars = Title + var chars = Title .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) - != UnicodeCategory.NonSpacingMark).ToArray(); + != UnicodeCategory.NonSpacingMark) + .ToArray(); Title = new string(chars).Normalize(NormalizationForm.FormC); - string SearchEngineFriendlyTitle = Title.ToLower(CultureInfo.CurrentCulture); + var SearchEngineFriendlyTitle = Title.ToLower(CultureInfo.CurrentCulture); // Remove all special characters from the string. SearchEngineFriendlyTitle = Regex.Replace(SearchEngineFriendlyTitle, @"[^A-Za-z0-9\s-]", ""); From 6317fc16ddf3270bd6561c32a2f63dae5c44fb10 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Thu, 7 Dec 2023 12:01:23 +0330 Subject: [PATCH 13/18] removing id from slug generator --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 10 +++++----- .../Features/Archive/ArchivePage.razor | 2 +- .../Features/Components/ShortBlogPost.razor | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index ceb7a1a4..768375dc 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -54,18 +54,18 @@ private string GenerateSlug() Title = new string(chars).Normalize(NormalizationForm.FormC); - var SearchEngineFriendlyTitle = Title.ToLower(CultureInfo.CurrentCulture); + var slug = Title.ToLower(CultureInfo.CurrentCulture); // Remove all special characters from the string. - SearchEngineFriendlyTitle = Regex.Replace(SearchEngineFriendlyTitle, @"[^A-Za-z0-9\s-]", ""); + slug = Regex.Replace(slug, @"[^A-Za-z0-9\s-]", ""); // Remove all additional spaces in favour of just one. - SearchEngineFriendlyTitle = Regex.Replace(SearchEngineFriendlyTitle, @"\s+", " ").Trim(); + slug = Regex.Replace(slug, @"\s+", " ").Trim(); // Replace all spaces with the hyphen. - SearchEngineFriendlyTitle = Regex.Replace(SearchEngineFriendlyTitle, @"\s", "-"); + slug = Regex.Replace(slug, @"\s", "-"); - return $"{Id}/{SearchEngineFriendlyTitle}"; + return slug; } public static BlogPost Create( diff --git a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor index e17c7802..08d176d5 100644 --- a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor @@ -21,7 +21,7 @@ } diff --git a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor index a84ffac7..cc59cafd 100644 --- a/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor +++ b/src/LinkDotNet.Blog.Web/Features/Components/ShortBlogPost.razor @@ -43,7 +43,7 @@

@MarkdownConverter.ToMarkupString(BlogPost.ShortDescription)

- Read the whole article + Read the whole article

From 88b274d249998667f614eec109cbcce9b66832b2 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Thu, 7 Dec 2023 12:48:52 +0330 Subject: [PATCH 14/18] generate Regex with source generators --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index 768375dc..27caa5fa 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -8,7 +8,7 @@ namespace LinkDotNet.Blog.Domain; -public sealed class BlogPost : Entity +public sealed partial class BlogPost : Entity { private BlogPost() { @@ -57,17 +57,35 @@ private string GenerateSlug() var slug = Title.ToLower(CultureInfo.CurrentCulture); // Remove all special characters from the string. - slug = Regex.Replace(slug, @"[^A-Za-z0-9\s-]", ""); + slug = MatchIfSpecialCharactersExist().Replace(slug, ""); // Remove all additional spaces in favour of just one. - slug = Regex.Replace(slug, @"\s+", " ").Trim(); + slug= MatchIfAdditionalSpacesExist().Replace(slug," ").Trim(); // Replace all spaces with the hyphen. - slug = Regex.Replace(slug, @"\s", "-"); + slug= MatchIfSpaceExist().Replace(slug, "-"); return slug; } + [GeneratedRegex( + @"[^A-Za-z0-9\s-]", + RegexOptions.CultureInvariant, + matchTimeoutMilliseconds: 1000)] + private static partial Regex MatchIfSpecialCharactersExist(); + + [GeneratedRegex( + @"\s+", + RegexOptions.CultureInvariant, + matchTimeoutMilliseconds: 1000)] + private static partial Regex MatchIfAdditionalSpacesExist(); + + [GeneratedRegex( + @"\s", + RegexOptions.CultureInvariant, + matchTimeoutMilliseconds: 1000)] + private static partial Regex MatchIfSpaceExist(); + public static BlogPost Create( string title, string shortDescription, From a72d354b5a6a900fc7c9397a3783f2e04102d590 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Thu, 14 Dec 2023 19:26:01 +0330 Subject: [PATCH 15/18] ShouldOpenBlogPost test fixed --- .../Web/Features/Components/ShortBlogPostTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Components/ShortBlogPostTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Components/ShortBlogPostTests.cs index 66a19356..c913b7b1 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Components/ShortBlogPostTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Components/ShortBlogPostTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Components; @@ -17,7 +17,7 @@ public void ShouldOpenBlogPost() var readMore = cut.Find(".read-more a"); - readMore.Attributes.Single(a => a.Name == "href").Value.Should().Be("/blogPost/SomeId"); + readMore.Attributes.Single(a => a.Name == "href").Value.Should().Be("/blogPost/SomeId/blogpost"); } [Fact] From 0a0b4d292b96178e7b02258a96102083ae3c1eca Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Thu, 14 Dec 2023 19:44:20 +0330 Subject: [PATCH 16/18] tests for slug added --- .../Domain/BlogPostTests.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs b/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs index d0422927..769a5d8d 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; @@ -24,6 +24,21 @@ public void ShouldUpdateBlogPost() blogPostToUpdate.PreviewImageUrlFallback.Should().Be("Url2"); blogPostToUpdate.IsPublished.Should().BeTrue(); blogPostToUpdate.Tags.Should().BeNullOrEmpty(); + blogPostToUpdate.Slug.Should().NotBeNull(); + } + + [Theory] + [InlineData("blog title","blog-title")] + [InlineData("blog title", "blog-title")] + [InlineData("blog +title", "blog-title")] + [InlineData("blog/title", "blogtitle")] + [InlineData("blog /title", "blog-title")] + public void ShouldGenerateValidSlug(string title, string expectedSlug) + { + var blogPost = BlogPost.Create(title, "Desc", "Content", "Url", true, previewImageUrlFallback: "Url2"); + blogPost.Id = "random-id"; + + blogPost.Slug.Should().Be(expectedSlug); } [Fact] From 0760553588b14a45ec56601592f799e7af54b418 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Sat, 16 Dec 2023 17:32:35 +0330 Subject: [PATCH 17/18] more testcase added for ShouldGenerateValidSlug --- .../LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs b/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs index 769a5d8d..dacb4448 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs @@ -33,10 +33,16 @@ public void ShouldUpdateBlogPost() [InlineData("blog +title", "blog-title")] [InlineData("blog/title", "blogtitle")] [InlineData("blog /title", "blog-title")] + [InlineData("BLOG TITLE", "blog-title")] + [InlineData("àccent", "accent")] + [InlineData("get 100$ quick", "get-100-quick")] + [InlineData("blog,title", "blogtitle")] + [InlineData("blog?!title", "blogtitle")] + [InlineData("blog----title", "blogtitle")] + [InlineData("überaus gut", "uberaus-gut")] public void ShouldGenerateValidSlug(string title, string expectedSlug) { - var blogPost = BlogPost.Create(title, "Desc", "Content", "Url", true, previewImageUrlFallback: "Url2"); - blogPost.Id = "random-id"; + var blogPost = new BlogPostBuilder().WithTitle(title).Build(); blogPost.Slug.Should().Be(expectedSlug); } From f19d7946c37397457ec09d8a932f1061c1cb4651 Mon Sep 17 00:00:00 2001 From: Kamyab7 Date: Sat, 16 Dec 2023 17:35:20 +0330 Subject: [PATCH 18/18] small issue fixed in MatchIfSpecialCharactersExist Regex --- src/LinkDotNet.Blog.Domain/BlogPost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LinkDotNet.Blog.Domain/BlogPost.cs b/src/LinkDotNet.Blog.Domain/BlogPost.cs index 27caa5fa..f4648f26 100644 --- a/src/LinkDotNet.Blog.Domain/BlogPost.cs +++ b/src/LinkDotNet.Blog.Domain/BlogPost.cs @@ -69,7 +69,7 @@ private string GenerateSlug() } [GeneratedRegex( - @"[^A-Za-z0-9\s-]", + @"[^A-Za-z0-9\s]", RegexOptions.CultureInvariant, matchTimeoutMilliseconds: 1000)] private static partial Regex MatchIfSpecialCharactersExist();