Skip to content

Commit

Permalink
Implemented custom slugs for articles
Browse files Browse the repository at this point in the history
  • Loading branch information
miawinter98 committed Feb 28, 2024
1 parent f1cb3d7 commit 1e10d41
Show file tree
Hide file tree
Showing 9 changed files with 718 additions and 12 deletions.
6 changes: 2 additions & 4 deletions Wave/Components/ArticleLink.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@using Wave.Data
@using System.Net
@using System.Web
@using Wave.Utilities

<a href="@Link" target="_top" @attributes="AdditionalAttributes">
@ChildContent
Expand All @@ -13,10 +14,7 @@
public RenderFragment? ChildContent { get; set; }

private string TitleEncoded => Uri.EscapeDataString(Article.Title.ToLowerInvariant()).Replace("-", "+").Replace("%20", "-");
private string Link =>
Article.PublishDate.Year >= 9999
? $"/article/{Article.Id}"
: $"/{Article.PublishDate.Year}/{Article.PublishDate.Month:D2}/{Article.PublishDate.Day:D2}/{TitleEncoded}";
private string Link => ArticleUtilities.GenerateArticleLink(Article, null);

[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }
Expand Down
6 changes: 4 additions & 2 deletions Wave/Components/Pages/ArticleView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@using System.Security.Claims
@using System.Diagnostics.CodeAnalysis
@using System.Globalization
@using System.Net
@using Microsoft.AspNetCore.Identity
@using Microsoft.Extensions.Options
@using Wave.Services
Expand Down Expand Up @@ -176,12 +177,13 @@
.FirstOrDefault(a => a.Id == Id);
} else if (Date is { } date && Title is { } title) {
using var context = ContextFactory.CreateDbContext();
Article = context.Set<Article>()
string? slug = TitleEncoded == null ? null : Uri.EscapeDataString(TitleEncoded);
Article ??= context.Set<Article>()
.IgnoreQueryFilters().Where(a => !a.IsDeleted)
.Include(a => a.Author)
.Include(a => a.Reviewer)
.Include(a => a.Categories)
.FirstOrDefault(a => a.PublishDate.Date == date.Date && a.Title.ToLower() == title);
.FirstOrDefault(a => a.PublishDate.Date == date.Date && (slug != null && a.Slug == slug || a.Title.ToLower() == title));
}
}

Expand Down
37 changes: 32 additions & 5 deletions Wave/Components/Pages/Partials/ArticleEditorPartial.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@using Wave.Data
@using System.ComponentModel.DataAnnotations
@using System.Diagnostics.CodeAnalysis
@using System.Net
@using Microsoft.AspNetCore.Identity
@using Microsoft.EntityFrameworkCore
@using Wave.Utilities
Expand All @@ -24,11 +25,22 @@
<DataAnnotationsValidator/>
<input type="hidden" @bind-value="@Model.Id"/>

<InputLabelComponent LabelText="@Localizer["Title_Label"]" For="() => Model.Title">
<InputText class="input input-bordered w-full" maxlength="256" required aria-required
@bind-Value="@Model.Title" placeholder="@Localizer["Title_Placeholder"]" autocomplete="off"/>
</InputLabelComponent>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-8">
<InputLabelComponent LabelText="@Localizer["Title_Label"]" For="() => Model.Title">
<InputText class="input input-bordered w-full" maxlength="256" required aria-required
@bind-Value="@Model.Title" placeholder="@Localizer["Title_Placeholder"]" autocomplete="off"/>
</InputLabelComponent>

<InputLabelComponent LabelText="@Localizer["Slug_Label"]" For="() => Model.Slug">
@if (Article.Status is not ArticleStatus.Published || Article.PublishDate >= DateTimeOffset.UtcNow) {
<InputText class="input input-bordered w-full" maxlength="64"
@bind-Value="@Model.Slug" placeholder="@Localizer["Slug_Placeholder"]" autocomplete="off"/>
} else {
<input class="input input-bordered w-full" readonly value="@Model.Slug"
placeholder="@Localizer["Slug_Placeholder"]" autocomplete="off" />
}
</InputLabelComponent>

<InputLabelComponent LabelText="@Localizer["PublishDate_Label"]" For="() => Model.PublishDate">
@if (Article.Status is not ArticleStatus.Published || Article.PublishDate >= DateTimeOffset.UtcNow) {
<InputDate class="input input-bordered w-full" Type="InputDateType.DateTimeLocal"
Expand Down Expand Up @@ -137,7 +149,7 @@
@Localizer["EditorSubmit"]
</button>
@if (Article.Id != Guid.Empty) {
<a class="btn w-full sm:btn-wide" href="/article/@(Article.Id)">
<a class="btn w-full sm:btn-wide" href="@ArticleUtilities.GenerateArticleLink(Article, null)">
@Localizer["ViewArticle_Label"]
</a>
}
Expand Down Expand Up @@ -247,6 +259,7 @@
if (article is not null) {
Model.Id ??= article.Id;
Model.Title ??= article.Title;
Model.Slug ??= article.Slug;
Model.Body ??= article.Body;
Model.PublishDate ??= article.PublishDate.LocalDateTime;
Model.Categories ??= article.Categories.Select(c => c.Id).ToArray();
Expand All @@ -267,6 +280,18 @@
if (Model.PublishDate is not null &&
(Article.Status is not ArticleStatus.Published || Article.PublishDate >= DateTimeOffset.UtcNow))
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)) {
Article.Slug = WebUtility.UrlEncode(Model.Slug);
} else if (string.IsNullOrWhiteSpace(Article.Slug)) {
Article.Slug = Uri.EscapeDataString(Article.Title.ToLowerInvariant())
.Replace("-", "+")
.Replace("%20", "-");
Article.Slug = Article.Slug[..Math.Min(64, Article.Slug.Length)];
Model.Slug = Article.Slug;
}

Article.LastModified = DateTimeOffset.UtcNow;
Article.BodyHtml = MarkdownUtilities.Parse(Article.Body);
Article.BodyPlain = HtmlUtilities.GetPlainText(Article.BodyHtml);
Expand Down Expand Up @@ -361,6 +386,8 @@

[Required(AllowEmptyStrings = false), MaxLength(256)]
public string? Title { get; set; }
[MaxLength(64)]
public string? Slug { get; set; }
[Required(AllowEmptyStrings = false)]
public string? Body { get; set; }

Expand Down
1 change: 1 addition & 0 deletions Wave/Data/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ protected override void OnModelCreating(ModelBuilder builder) {
article.HasKey(a => a.Id);
article.Property(a => a.Title)
.IsRequired().HasMaxLength(256);
article.Property(a => a.Slug).HasMaxLength(64).IsRequired().HasDefaultValue("");

article.HasOne(a => a.Author).WithMany(a => a.Articles)
.IsRequired().OnDelete(DeleteBehavior.Cascade);
Expand Down
3 changes: 3 additions & 0 deletions Wave/Data/Article.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class Article : ISoftDelete {
public string BodyHtml { get; set; } = string.Empty;
public string BodyPlain { get; set; } = string.Empty;

[MaxLength(64)]
public string Slug { get; set; } = string.Empty;

public required ApplicationUser Author { get; set; }
public ApplicationUser? Reviewer { get; set; }

Expand Down
Loading

0 comments on commit 1e10d41

Please sign in to comment.