feat: add update time.
All checks were successful
Build blog docker image / Build-Blog-Image (push) Successful in 3m57s

Make the link in blog blue.
This commit is contained in:
2026-01-07 23:21:09 +08:00
parent 1be39327aa
commit 80e48a2043
7 changed files with 64 additions and 17 deletions

View File

@@ -6,5 +6,11 @@ public interface IEssayScanService
{ {
public Task<BlogContents> ScanContents(); public Task<BlogContents> ScanContents();
/// <summary>
/// 将对应的博客文章保存在磁盘上
/// </summary>
/// <param name="content"></param>
/// <param name="isDraft">指定对应博客文章是否为草稿。因为BlogContent是不可变对象因此提供该参数以方便publish的实现。</param>
/// <returns></returns>
public Task SaveBlogContent(BlogContent content, bool isDraft = true); public Task SaveBlogContent(BlogContent content, bool isDraft = true);
} }

View File

@@ -19,6 +19,7 @@ public sealed class YaeBlogCommand
AddWatchCommand(_rootCommand); AddWatchCommand(_rootCommand);
AddListCommand(_rootCommand); AddListCommand(_rootCommand);
AddNewCommand(_rootCommand); AddNewCommand(_rootCommand);
AddUpdateCommand(_rootCommand);
AddPublishCommand(_rootCommand); AddPublishCommand(_rootCommand);
AddScanCommand(_rootCommand); AddScanCommand(_rootCommand);
AddCompressCommand(_rootCommand); AddCompressCommand(_rootCommand);
@@ -46,7 +47,7 @@ public sealed class YaeBlogCommand
WebApplication application = builder.Build(); WebApplication application = builder.Build();
application.UseStaticFiles(); application.MapStaticAssets();
application.UseAntiforgery(); application.UseAntiforgery();
application.UseYaeBlog(); application.UseYaeBlog();
@@ -76,7 +77,7 @@ public sealed class YaeBlogCommand
WebApplication application = builder.Build(); WebApplication application = builder.Build();
application.UseStaticFiles(); application.MapStaticAssets();
application.UseAntiforgery(); application.UseAntiforgery();
application.UseYaeBlog(); application.UseYaeBlog();
@@ -109,7 +110,7 @@ public sealed class YaeBlogCommand
await essayScanService.SaveBlogContent(new BlogContent( await essayScanService.SaveBlogContent(new BlogContent(
new FileInfo(Path.Combine(blogOption.Value.Root, "drafts", file + ".md")), new FileInfo(Path.Combine(blogOption.Value.Root, "drafts", file + ".md")),
new MarkdownMetadata { Title = file, Date = DateTime.Now }, new MarkdownMetadata { Title = file, Date = DateTimeOffset.Now, UpdateTime = DateTimeOffset.Now },
string.Empty, true, [], [])); string.Empty, true, [], []));
Console.WriteLine($"Created new blog '{file}."); Console.WriteLine($"Created new blog '{file}.");
@@ -117,6 +118,33 @@ public sealed class YaeBlogCommand
new EssayScanServiceBinder()); new EssayScanServiceBinder());
} }
private static void AddUpdateCommand(RootCommand rootCommand)
{
Command newCommand = new("update", "Update the blog essay.");
rootCommand.AddCommand(newCommand);
Argument<string> filenameArgument = new(name: "blog name", description: "The blog filename to update.");
newCommand.AddArgument(filenameArgument);
newCommand.SetHandler(async (file, _, _, essayScanService) =>
{
Console.WriteLine("HINT: The update command only consider published blogs.");
BlogContents contents = await essayScanService.ScanContents();
BlogContent? content = contents.Posts.FirstOrDefault(c => c.BlogName == file);
if (content is null)
{
Console.WriteLine($"Target essay {file} is not exist.");
return;
}
content.Metadata.UpdateTime = DateTimeOffset.Now;
await essayScanService.SaveBlogContent(content, content.IsDraft);
}, filenameArgument,
new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
}
private static void AddListCommand(RootCommand rootCommand) private static void AddListCommand(RootCommand rootCommand)
{ {
Command command = new("list", "List all blogs"); Command command = new("list", "List all blogs");

View File

@@ -19,10 +19,6 @@
<div class="px-6 pt-4 pb-2"> <div class="px-6 pt-4 pb-2">
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<div class="font-light">
@(_essay!.PublishTime.ToString("yyyy-MM-dd"))
</div>
@foreach (string tag in _essay!.Tags) @foreach (string tag in _essay!.Tags)
{ {
<div class="text-sky-500"> <div class="text-sky-500">
@@ -32,10 +28,19 @@
</div> </div>
} }
</div> </div>
</div>
<div class="px-6 pt-2 pb-4"> <div class="font-light py-1">
<div class="font-light"> 发布于: @(_essay.PublishTime.ToString("yyyy年MM月dd日 hh:mm:ss"))
</div>
@if (_essay.UpdateTime != _essay.PublishTime)
{
<div class="font-light pb-1">
更新于: @(_essay.UpdateTime.ToString("yyyy年MM月dd日 hh:mm:ss"))
</div>
}
<div class="font-light pb-1">
总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。 总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。
</div> </div>
</div> </div>

View File

@@ -8,7 +8,9 @@ public class BlogEssay : IComparable<BlogEssay>
public required bool IsDraft { get; init; } public required bool IsDraft { get; init; }
public required DateTime PublishTime { get; init; } public required DateTimeOffset PublishTime { get; init; }
public required DateTimeOffset UpdateTime { get; init; }
public required string Description { get; init; } public required string Description { get; init; }
@@ -28,6 +30,7 @@ public class BlogEssay : IComparable<BlogEssay>
FileName = FileName, FileName = FileName,
IsDraft = IsDraft, IsDraft = IsDraft,
PublishTime = PublishTime, PublishTime = PublishTime,
UpdateTime = UpdateTime,
Description = Description, Description = Description,
WordCount = WordCount, WordCount = WordCount,
ReadTime = ReadTime, ReadTime = ReadTime,

View File

@@ -4,7 +4,9 @@ public class MarkdownMetadata
{ {
public string? Title { get; set; } public string? Title { get; set; }
public DateTime? Date { get; set; } public DateTimeOffset Date { get; set; }
public DateTimeOffset UpdateTime { get; set; }
public List<string>? Tags { get; set; } public List<string>? Tags { get; set; }
} }

View File

@@ -16,8 +16,7 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
public async Task<BlogEssay> ProcessAsync(BlogEssay essay) public async Task<BlogEssay> ProcessAsync(BlogEssay essay)
{ {
BrowsingContext context = new(Configuration.Default); BrowsingContext context = new(Configuration.Default);
IDocument document = await context.OpenAsync( IDocument document = await context.OpenAsync(req => req.Content(essay.HtmlContent));
req => req.Content(essay.HtmlContent));
ApplyGlobalCssStyles(document); ApplyGlobalCssStyles(document);
BeatifyTable(document); BeatifyTable(document);
@@ -36,6 +35,7 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
{ "h5", "text-lg font-bold py-1" }, { "h5", "text-lg font-bold py-1" },
{ "p", "p-2" }, { "p", "p-2" },
{ "img", "w-11/12 block mx-auto my-2 rounded-md shadow-md" }, { "img", "w-11/12 block mx-auto my-2 rounded-md shadow-md" },
{ "a", "text-blue-600" }
}; };
private void ApplyGlobalCssStyles(IDocument document) private void ApplyGlobalCssStyles(IDocument document)

View File

@@ -47,7 +47,10 @@ public partial class RendererService(
Description = GetDescription(content), Description = GetDescription(content),
WordCount = wordCount, WordCount = wordCount,
ReadTime = CalculateReadTime(wordCount), ReadTime = CalculateReadTime(wordCount),
PublishTime = content.Metadata.Date ?? DateTime.Now, PublishTime = content.Metadata.Date == default ? DateTimeOffset.Now : content.Metadata.Date,
// 如果不存在最后的更新时间,就把更新时间设置为发布时间
UpdateTime =
content.Metadata.UpdateTime == default ? content.Metadata.Date : content.Metadata.UpdateTime,
HtmlContent = content.Content HtmlContent = content.Content
}; };
@@ -182,7 +185,7 @@ public partial class RendererService(
string description = builder.ToString(); string description = builder.ToString();
logger.LogDebug("Description of {} is {}.", content.BlogName, logger.LogDebug("Description of {name} is {desc}.", content.BlogName,
description); description);
return description; return description;
} }
@@ -193,7 +196,7 @@ public partial class RendererService(
where char.IsLetterOrDigit(c) where char.IsLetterOrDigit(c)
select c).Count(); select c).Count();
logger.LogDebug("Word count of {} is {}", content.BlogName, logger.LogDebug("Word count of {blog} is {count}", content.BlogName,
count); count);
return (uint)count; return (uint)count;
} }