From e1cab9b53fc66da0b32391e7cff28900d3e4d380 Mon Sep 17 00:00:00 2001 From: Mia Winter Date: Tue, 30 Apr 2024 14:48:58 +0200 Subject: [PATCH] Improved article body formatting --- .../Pages/Partials/ArticleEditorPartial.razor | 17 +--- Wave/Data/Article.cs | 77 ++++++++++++------- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Wave/Components/Pages/Partials/ArticleEditorPartial.razor b/Wave/Components/Pages/Partials/ArticleEditorPartial.razor index f3fea71..e76fc5b 100644 --- a/Wave/Components/Pages/Partials/ArticleEditorPartial.razor +++ b/Wave/Components/Pages/Partials/ArticleEditorPartial.razor @@ -188,24 +188,13 @@ Article.PublishDate = Model.PublishDate.Value; if (Article.Status is ArticleStatus.Published && Article.PublishDate < DateTimeOffset.Now) { // can't change slugs when the article is public - } else if (!string.IsNullOrWhiteSpace(Model.Slug) || string.IsNullOrWhiteSpace(Article.Slug)) { - string baseSlug = Model.Slug ?? Article.Title; - baseSlug = baseSlug.ToLowerInvariant()[..Math.Min(64, baseSlug.Length)]; - string slug = Uri.EscapeDataString(baseSlug).Replace("-", "+").Replace("%20", "-"); - // if our escaping increases the slug length, there is a chance it ends with an escape - // character, so if this overshoot is not divisible by 3, then we risk cutting of the - // escape character, so we need to remove it in it's entirely if that's the case - int escapeTrimOvershoot = Math.Max(0, 3 - (slug.Length - baseSlug.Length) % 3); - // if the slug already fits 64 character, there will be no cutoff in the next operation anyway, - // so we don't need to fix what is described in the previous comment - if (slug.Length <= 64) escapeTrimOvershoot = 0; - Article.Slug = slug[..Math.Min(slug.Length, 64 - escapeTrimOvershoot)]; + } else { + Article.UpdateSlug(Model.Slug); Model.Slug = Article.Slug; } Article.LastModified = DateTimeOffset.UtcNow; - Article.BodyHtml = MarkdownUtilities.Parse(Article.Body); - Article.BodyPlain = HtmlUtilities.GetPlainText(Article.BodyHtml); + Article.UpdateBody(); await using var context = await ContextFactory.CreateDbContextAsync(); diff --git a/Wave/Data/Article.cs b/Wave/Data/Article.cs index 2711ab4..0cba687 100644 --- a/Wave/Data/Article.cs +++ b/Wave/Data/Article.cs @@ -1,46 +1,69 @@ using System.ComponentModel.DataAnnotations; +using Wave.Utilities; namespace Wave.Data; public enum ArticleStatus { - Draft = 0, - InReview = 1, - Published = 2 + Draft = 0, + InReview = 1, + Published = 2 } // TODO:: Add tags for MVP ? // TODO:: Archive System (Notice / Redirect to new content?) (Deprecation date?) public class Article : ISoftDelete { - [Key] - public Guid Id { get; set; } - public bool IsDeleted { get; set; } + [Key] + public Guid Id { get; set; } + public bool IsDeleted { get; set; } - // Computed - public bool CanBePublic { get; set; } + // Computed + public bool CanBePublic { get; set; } - [MaxLength(256)] - public required string Title { get; set; } - public required string Body { get; set; } - public string BodyHtml { get; set; } = string.Empty; - public string BodyPlain { get; set; } = string.Empty; + [MaxLength(256)] + public required string Title { get; set; } + // ReSharper disable thrice EntityFramework.ModelValidation.UnlimitedStringLength + public required string Body { get; set; } + public string BodyHtml { get; set; } = string.Empty; + public string BodyPlain { get; set; } = string.Empty; - [MaxLength(64)] - public string Slug { get; set; } = string.Empty; + [MaxLength(64)] + public string Slug { get; set; } = string.Empty; - public required ApplicationUser Author { get; set; } - public ApplicationUser? Reviewer { get; set; } + public required ApplicationUser Author { get; set; } + public ApplicationUser? Reviewer { get; set; } - public ArticleStatus Status { get; set; } - public DateTimeOffset CreationDate { get; set; } = DateTimeOffset.Now; - public DateTimeOffset PublishDate { get; set; } = DateTimeOffset.MaxValue; - public DateTimeOffset? LastModified { get; set; } + public ArticleStatus Status { get; set; } + public DateTimeOffset CreationDate { get; set; } = DateTimeOffset.Now; + public DateTimeOffset PublishDate { get; set; } = DateTimeOffset.MaxValue; + public DateTimeOffset? LastModified { get; set; } - /// - /// Returns LastModified if it's after the articles PublishDate, otherwise gives you the PublishDate - /// - public DateTimeOffset LastPublicChange => LastModified > PublishDate ? LastModified.Value : PublishDate; + /// + /// Returns LastModified if it's after the articles PublishDate, otherwise gives you the PublishDate + /// + public DateTimeOffset LastPublicChange => LastModified > PublishDate ? LastModified.Value : PublishDate; - public IList Categories { get; } = []; - public IList Images { get; } = []; + public IList Categories { get; } = []; + public IList Images { get; } = []; + + public void UpdateSlug(string? potentialNewSlug) { + if (string.IsNullOrWhiteSpace(potentialNewSlug) && !string.IsNullOrWhiteSpace(Slug)) return; + + string baseSlug = potentialNewSlug ?? Title; + baseSlug = baseSlug.ToLowerInvariant()[..Math.Min(64, baseSlug.Length)]; + string slug = Uri.EscapeDataString(baseSlug).Replace("-", "+").Replace("%20", "-"); + // if our escaping increases the slug length, there is a chance it ends with an escape + // character, so if this overshoot is not divisible by 3, then we risk cutting of the + // escape character, so we need to remove it in it's entirely if that's the case + int escapeTrimOvershoot = Math.Max(0, 3 - (slug.Length - baseSlug.Length) % 3); + // if the slug already fits 64 character, there will be no cutoff in the next operation anyway, + // so we don't need to fix what is described in the previous comment + if (slug.Length <= 64) escapeTrimOvershoot = 0; + Slug = slug[..Math.Min(slug.Length, 64 - escapeTrimOvershoot)]; + } + + public void UpdateBody() { + BodyHtml = MarkdownUtilities.Parse(Body).Trim(); + BodyPlain = HtmlUtilities.GetPlainText(BodyHtml).Trim(); + } } \ No newline at end of file