feat: 美化文章界面 #3

Merged
jackfiled merged 8 commits from feat-markdown into master 2024-07-29 22:32:27 +08:00
12 changed files with 119 additions and 60 deletions
Showing only changes of commit 131a966940 - Show all commits

View File

@ -0,0 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using YaeBlog.Core.Models;
namespace YaeBlog.Core.Abstractions;
public interface IEssayContentService
{
public IReadOnlyDictionary<string, BlogEssay> Essays { get; }
public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags { get; }
public bool SearchByUrlEncodedTag(string tag,[NotNullWhen(true)] out List<BlogEssay>? result);
}

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using YaeBlog.Core.Abstractions;
using YaeBlog.Core.Models; using YaeBlog.Core.Models;
using YaeBlog.Core.Processors; using YaeBlog.Core.Processors;
using YaeBlog.Core.Services; using YaeBlog.Core.Services;
@ -20,6 +21,8 @@ public static class WebApplicationBuilderExtensions
builder.Services.AddSingleton<EssayScanService>(); builder.Services.AddSingleton<EssayScanService>();
builder.Services.AddSingleton<RendererService>(); builder.Services.AddSingleton<RendererService>();
builder.Services.AddSingleton<EssayContentService>(); builder.Services.AddSingleton<EssayContentService>();
builder.Services.AddSingleton<IEssayContentService, EssayContentService>(provider =>
provider.GetRequiredService<EssayContentService>());
builder.Services.AddTransient<ImagePostRenderProcessor>(); builder.Services.AddTransient<ImagePostRenderProcessor>();
builder.Services.AddTransient<BlogOptions>(provider => builder.Services.AddTransient<BlogOptions>(provider =>
provider.GetRequiredService<IOptions<BlogOptions>>().Value); provider.GetRequiredService<IOptions<BlogOptions>>().Value);

View File

@ -0,0 +1,16 @@
using System.Text.Encodings.Web;
namespace YaeBlog.Core.Models;
public class EssayTag(string tagName) : IEquatable<EssayTag>
{
public string TagName { get; } = tagName;
public string UrlEncodedTagName { get; } = UrlEncoder.Default.Encode(tagName);
public bool Equals(EssayTag? other) => other is not null && TagName == other.TagName;
public override bool Equals(object? obj) => obj is EssayTag other && Equals(other);
public override int GetHashCode() => TagName.GetHashCode();
}

View File

@ -1,57 +1,48 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using YaeBlog.Core.Abstractions;
using YaeBlog.Core.Models; using YaeBlog.Core.Models;
namespace YaeBlog.Core.Services; namespace YaeBlog.Core.Services;
public class EssayContentService public class EssayContentService : IEssayContentService
{ {
private readonly ConcurrentDictionary<string, BlogEssay> _essays = new(); private readonly ConcurrentDictionary<string, BlogEssay> _essays = new();
private readonly Dictionary<string, List<BlogEssay>> _tags = []; private readonly Dictionary<EssayTag, List<BlogEssay>> _tags = [];
public bool TryGet(string key, out BlogEssay? essay) public bool TryAdd(BlogEssay essay) => _essays.TryAdd(essay.FileName, essay);
=> _essays.TryGetValue(key, out essay);
public bool TryAdd(string key, BlogEssay essay) => _essays.TryAdd(key, essay); public IReadOnlyDictionary<string, BlogEssay> Essays => _essays;
public IDictionary<string, BlogEssay> Essays => _essays; public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags => _tags;
public void RefreshTags() public void RefreshTags()
{ {
foreach (BlogEssay essay in _essays.Values) _tags.Clear();
{
foreach (string tag in essay.Tags)
{
if (_tags.TryGetValue(tag, out var list))
{
list.Add(essay);
}
else
{
_tags[tag] = [essay];
}
}
}
foreach (KeyValuePair<string,List<BlogEssay>> pair in _tags) foreach (BlogEssay essay in _essays.Values)
{ {
pair.Value.Sort(); foreach (EssayTag essayTag in essay.Tags.Select(tag => new EssayTag(tag)))
} {
if (_tags.TryGetValue(essayTag, out List<BlogEssay>? essays))
{
essays.Add(essay);
}
else
{
_tags.Add(essayTag, [essay]);
}
}
}
} }
public IEnumerable<KeyValuePair<string, int>> Tags => from item in _tags public bool SearchByUrlEncodedTag(string tag, [NotNullWhen(true)] out List<BlogEssay>? result)
orderby item.Value.Count descending
select KeyValuePair.Create(item.Key, item.Value.Count);
public int TagCount => _tags.Count;
public IEnumerable<BlogEssay> GetTag(string tag)
{ {
if (_tags.TryGetValue(tag, out var list)) result = (from item in _tags
{ where item.Key.UrlEncodedTagName == tag
return list; select item.Value).FirstOrDefault();
}
throw new KeyNotFoundException("Selected tag not found."); return result is not null;
} }
} }

View File

@ -125,7 +125,7 @@ public class RendererService(ILogger<RendererService> logger,
essay = await processor.ProcessAsync(essay); essay = await processor.ProcessAsync(essay);
} }
if (!essayContentService.TryAdd(essay.FileName, essay)) if (!essayContentService.TryAdd(essay))
{ {
throw new BlogFileException( throw new BlogFileException(
$"There are two essays with the same name: '{essay.FileName}'."); $"There are two essays with the same name: '{essay.FileName}'.");

View File

@ -1,7 +1,7 @@
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models @using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject EssayContentService EssayContentInstance @inject IEssayContentService Contents
@inject BlogOptions Options @inject BlogOptions Options
<div class="container"> <div class="container">
@ -24,7 +24,7 @@
<div class="col-auto"> <div class="col-auto">
<a href="/blog/archives"> <a href="/blog/archives">
@(EssayContentInstance.Essays.Count) @(Contents.Essays.Count)
</a> </a>
</div> </div>
</div> </div>
@ -36,7 +36,7 @@
<div class="col-auto"> <div class="col-auto">
<a href="/blog/tags"> <a href="/blog/tags">
@(EssayContentInstance.TagCount) @(Contents.Tags.Count)
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,5 +1,9 @@
@page "/blog/about" @page "/blog/about"
<PageTitle>
关于
</PageTitle>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col"> <div class="col">

View File

@ -1,8 +1,12 @@
@page "/blog/archives" @page "/blog/archives"
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models @using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject EssayContentService EssayContentInstance @inject IEssayContentService Contents
<PageTitle>
归档
</PageTitle>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
@ -64,9 +68,8 @@
{ {
base.OnInitialized(); base.OnInitialized();
_essays.AddRange(from essay in EssayContentInstance.Essays _essays.AddRange(from essay in Contents.Essays
orderby essay.Value.PublishTime descending orderby essay.Value.PublishTime descending
group essay by new DateTime(essay.Value.PublishTime.Year, 1, 1)); group essay by new DateTime(essay.Value.PublishTime.Year, 1, 1));
} }
} }

View File

@ -1,8 +1,8 @@
@page "/blog" @page "/blog"
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models @using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject EssayContentService EssayContentInstance @inject IEssayContentService Contents
@inject NavigationManager NavigationInstance @inject NavigationManager NavigationInstance
<PageTitle> <PageTitle>
@ -99,15 +99,15 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
_page = Page ?? 1; _page = Page ?? 1;
_pageCount = EssayContentInstance.Essays.Count / EssaysPerPage + 1; _pageCount = Contents.Essays.Count / EssaysPerPage + 1;
if (EssaysPerPage * _page > EssayContentInstance.Essays.Count + EssaysPerPage) if (EssaysPerPage * _page > Contents.Essays.Count + EssaysPerPage)
{ {
NavigationInstance.NavigateTo("/NotFount"); NavigationInstance.NavigateTo("/NotFount");
return; return;
} }
_essays.AddRange(EssayContentInstance.Essays _essays.AddRange(Contents.Essays
.OrderByDescending(p => p.Value.PublishTime) .OrderByDescending(p => p.Value.PublishTime)
.Skip((_page - 1) * EssaysPerPage) .Skip((_page - 1) * EssaysPerPage)
.Take(EssaysPerPage)); .Take(EssaysPerPage));

View File

@ -1,9 +1,9 @@
@page "/blog/essays/{BlogKey}" @page "/blog/essays/{BlogKey}"
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models @using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject IEssayContentService Contents
@inject NavigationManager NavigationInstance @inject NavigationManager NavigationInstance
@inject EssayContentService EssayContentInstance
<PageTitle> <PageTitle>
@(_essay!.Title) @(_essay!.Title)
@ -52,7 +52,7 @@
return; return;
} }
if (!EssayContentInstance.TryGet(BlogKey, out _essay)) if (!Contents.Essays.TryGetValue(BlogKey, out _essay))
{ {
NavigationInstance.NavigateTo("/NotFound"); NavigationInstance.NavigateTo("/NotFound");
} }

View File

@ -1,5 +1,9 @@
@page "/NotFound" @page "/NotFound"
<PageTitle>
啊~ 页面走丢啦~
</PageTitle>
<div class="container"> <div class="container">
<h3>NotFound!</h3> <h3>NotFound!</h3>
</div> </div>

View File

@ -1,8 +1,14 @@
@page "/blog/tags/" @page "/blog/tags/"
@using System.Text.Encodings.Web
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models @using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject EssayContentService EssayContentInstance @inject IEssayContentService Contents
@inject NavigationManager NavigationInstance
<PageTitle>
@(TagName ?? "标签")
</PageTitle>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
@ -28,18 +34,19 @@
{ {
<div> <div>
<ul> <ul>
@foreach (KeyValuePair<string, int> pair in EssayContentInstance.Tags) @foreach (KeyValuePair<EssayTag, List<BlogEssay>> pair in
Contents.Tags.OrderByDescending(pair => pair.Value.Count))
{ {
<li class="p-2"> <li class="p-2">
<a href="/blog/tags/?tagName=@(pair.Key)"> <a href="/blog/tags/?tagName=@(pair.Key.UrlEncodedTagName)">
<div class="container fs-5"> <div class="container fs-5">
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
# @(pair.Key) # @(pair.Key.TagName)
</div> </div>
<div class="col-auto tag-count"> <div class="col-auto tag-count">
@(pair.Value) @(pair.Value.Count)
</div> </div>
</div> </div>
</div> </div>
@ -52,7 +59,7 @@
else else
{ {
<div class="container"> <div class="container">
@foreach (BlogEssay essay in EssayContentInstance.GetTag(TagName).OrderByDescending(e => e.PublishTime)) @foreach (BlogEssay essay in _essays)
{ {
<EssayCard Essay="@essay"/> <EssayCard Essay="@essay"/>
} }
@ -63,5 +70,23 @@
@code { @code {
[SupplyParameterFromQuery] public string? TagName { get; set; } [SupplyParameterFromQuery] public string? TagName { get; set; }
private readonly List<BlogEssay> _essays = [];
protected override void OnInitialized()
{
base.OnInitialized();
if (string.IsNullOrEmpty(TagName))
{
return;
}
if (!Contents.SearchByUrlEncodedTag(UrlEncoder.Default.Encode(TagName), out List<BlogEssay>? essays))
{
NavigationInstance.NavigateTo("/NotFound");
return;
}
_essays.AddRange(essays.OrderDescending());
}
} }