diff --git a/YaeBlog/Components/PageAnchor.razor b/src/YaeBlog/Components/PageAnchor.razor
similarity index 100%
rename from YaeBlog/Components/PageAnchor.razor
rename to src/YaeBlog/Components/PageAnchor.razor
diff --git a/src/YaeBlog/Components/Pages/About.razor b/src/YaeBlog/Components/Pages/About.razor
new file mode 100644
index 0000000..3e4821e
--- /dev/null
+++ b/src/YaeBlog/Components/Pages/About.razor
@@ -0,0 +1,99 @@
+@page "/about"
+
+
+ 关于
+
+
+
+
+
关于
+
+
+
+ 把字刻在石头上!(・’ω’・)
+
+
+
+
+
+
关于我
+
+
+
+
+
+ 正在明光村幼儿园附属研究生院攻读计算机科学与技术的硕士学位,研究AI编译器和异构编译器。
+
+
+
+ 一般在互联网上使用初冬的朝阳 或者
+ jackfiled 的名字活动。
+ (都是ICP备案过的人了,网名似乎没有太大的用处)
+
+
+ Fun Fact:jackfiled 这个名字来自于2020年我使用链接在树莓派上的9英寸屏幕注册
+ GitHub的一时兴起,并没有任何特定的含义。
+ 初冬的朝阳 则是源自初中,具体典故已不可考。
+ 至少到目前为止,还没有在要求唯一ID的平台遇见重名的情况。
+ 我的真实名字似乎也是如此。
+
+
+
+
+
+ 主要是一个.NET程序员,目前也在尝试写一点Rust。
+
+ 总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。
+
+
+
+ 写过一些前后端分离的项目,对于RISC-V相关的开发项目也颇感兴趣。
+
+
+ 常常因为现实的压力而写一些C/C++,现在就在和MLIR殊死搏斗。
+
+
+ 日常使用Arch Linux,KISS的原则深得我心。
+
+
+
+
+
+ 100%社恐。日常生活是宅在电脑前面自言自语。
+
+
+ 兴趣活动是读书和看番,目前在玩戴森球计划和三角洲。2022年~2024年的时候沉迷于原神,现在偶尔还会登上去过一过剧情。
+
+
+
+
+
+
+
+
关于本站
+
+
+
+
+
+ 本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用
+
+ 的。
+
+
+
+
+
+ 2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。
+
+
+
+
+
+ 2025年,我们将使用的样式库从Bootstrap迁移到Tailwind CSS,将现代的前端技术同Blazor结合起来。
+
+
+
+
+
+
diff --git a/YaeBlog/Components/Pages/Archives.razor b/src/YaeBlog/Components/Pages/Archives.razor
similarity index 100%
rename from YaeBlog/Components/Pages/Archives.razor
rename to src/YaeBlog/Components/Pages/Archives.razor
diff --git a/YaeBlog/Components/Pages/BlogIndex.razor b/src/YaeBlog/Components/Pages/BlogIndex.razor
similarity index 98%
rename from YaeBlog/Components/Pages/BlogIndex.razor
rename to src/YaeBlog/Components/Pages/BlogIndex.razor
index c5766c5..f4ff07e 100644
--- a/YaeBlog/Components/Pages/BlogIndex.razor
+++ b/src/YaeBlog/Components/Pages/BlogIndex.razor
@@ -6,7 +6,7 @@
@inject NavigationManager NavigationInstance
- Ricardo's Blog
+ Jackfiled's Blog
diff --git a/YaeBlog/Components/Pages/Essays.razor b/src/YaeBlog/Components/Pages/Essays.razor
similarity index 100%
rename from YaeBlog/Components/Pages/Essays.razor
rename to src/YaeBlog/Components/Pages/Essays.razor
diff --git a/YaeBlog/Components/Pages/Friends.razor b/src/YaeBlog/Components/Pages/Friends.razor
similarity index 85%
rename from YaeBlog/Components/Pages/Friends.razor
rename to src/YaeBlog/Components/Pages/Friends.razor
index b46369f..648a198 100644
--- a/YaeBlog/Components/Pages/Friends.razor
+++ b/src/YaeBlog/Components/Pages/Friends.razor
@@ -1,6 +1,7 @@
@page "/friends"
+@using Microsoft.Extensions.Options
@using YaeBlog.Models
-@inject BlogOptions Options
+@inject IOptions
BlogOptionInstance
友链
@@ -18,7 +19,7 @@
- @foreach (FriendLink link in Options.Links)
+ @foreach (FriendLink link in BlogOptionInstance.Value.Links.Where(i => i is not null).Select(i => i!))
{
-
-@code {
-
-}
diff --git a/src/YaeBlog/Components/Pages/Index.razor b/src/YaeBlog/Components/Pages/Index.razor
new file mode 100644
index 0000000..33393a7
--- /dev/null
+++ b/src/YaeBlog/Components/Pages/Index.razor
@@ -0,0 +1,101 @@
+@page "/"
+@using YaeBlog.Abstraction
+@using YaeBlog.Models
+@inject IEssayContentService EssayContentInstance
+
+
+ Jackfiled's Index
+
+
+
+
+
+
+
+
+
+
+
+
+
恕我不能亲自为您沏茶,还是非常欢迎您来,能在广阔的互联网世界中发现这里实属不易。
+
+
+
+
+ 正在攻读计算机科学与技术的硕士学位,研究方向是AI编译和异构编译!
+ 喜欢优雅的代码,香甜的蛋糕等等一切可爱的事物。
+ 更多的情报请见 。
+
+
+
+
+ 中收集了我的各种奇思妙想,如果感兴趣欢迎移步。
+ @if (_latestEssay is not null)
+ {
+
+ 最新的一期博客关注 。
+
+ }
+
+
+ 日常的代码开发使用自建的 进行,个人
+ 开发的各种项目都可以在上面找到。下面的热力图展示了我在Git上的各种动态(Everything as Code) 。
+
+
+
+
+
+
+
+
+@code {
+ private BlogEssay? _latestEssay;
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ _latestEssay = EssayContentInstance.Essays.OrderByDescending(e => e.UpdateTime).FirstOrDefault();
+ }
+
+}
diff --git a/src/YaeBlog/Components/Pages/Index.razor.css b/src/YaeBlog/Components/Pages/Index.razor.css
new file mode 100644
index 0000000..53f2acd
--- /dev/null
+++ b/src/YaeBlog/Components/Pages/Index.razor.css
@@ -0,0 +1,47 @@
+.fa-brands {
+ font-family: "Font Awesome 7 Brands", "Font Awesome 7 Free";
+ font-style: normal;
+ font-synthesis: none;
+ font-variant: normal;
+ line-height: 1;
+ text-rendering: auto;
+ font-weight: 400;
+}
+
+.fa-github::before {
+ content: "\f09b";
+ color: #24292e;
+}
+
+.fa-bilibili::before {
+ content: "\e3d9";
+ color: #00AEEC;
+}
+
+.gitea-icon {
+ display: inline-block;
+ vertical-align: -0.125em;
+ width: 1em;
+ height: 1em;
+
+ background-image: url("https://docs.gitea.com/img/gitea.svg");
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+
+ user-select: none;
+}
+
+.rednote-icon {
+ display: inline-block;
+ vertical-align: -0.125em;
+ width: 1em;
+ height: 1em;
+
+ background-image: url("images/xiaohongshu-seeklogo.svg");
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+
+ user-select: none;
+}
diff --git a/YaeBlog/Components/Pages/NotFound.razor b/src/YaeBlog/Components/Pages/NotFound.razor
similarity index 100%
rename from YaeBlog/Components/Pages/NotFound.razor
rename to src/YaeBlog/Components/Pages/NotFound.razor
diff --git a/YaeBlog/Components/Pages/Tags.razor b/src/YaeBlog/Components/Pages/Tags.razor
similarity index 100%
rename from YaeBlog/Components/Pages/Tags.razor
rename to src/YaeBlog/Components/Pages/Tags.razor
diff --git a/YaeBlog/Components/Pagination.razor b/src/YaeBlog/Components/Pagination.razor
similarity index 100%
rename from YaeBlog/Components/Pagination.razor
rename to src/YaeBlog/Components/Pagination.razor
diff --git a/YaeBlog/Components/Routes.razor b/src/YaeBlog/Components/Routes.razor
similarity index 100%
rename from YaeBlog/Components/Routes.razor
rename to src/YaeBlog/Components/Routes.razor
diff --git a/YaeBlog/Components/_Imports.razor b/src/YaeBlog/Components/_Imports.razor
similarity index 85%
rename from YaeBlog/Components/_Imports.razor
rename to src/YaeBlog/Components/_Imports.razor
index e1cbac4..0526866 100644
--- a/YaeBlog/Components/_Imports.razor
+++ b/src/YaeBlog/Components/_Imports.razor
@@ -7,3 +7,5 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using YaeBlog.Components
+@using BlazorSvgComponents
+@using BlazorSvgComponents.Models
diff --git a/YaeBlog/Controllers/FilesController.cs b/src/YaeBlog/Controllers/FilesController.cs
similarity index 100%
rename from YaeBlog/Controllers/FilesController.cs
rename to src/YaeBlog/Controllers/FilesController.cs
diff --git a/YaeBlog/Directory.Build.targets b/src/YaeBlog/Directory.Build.targets
similarity index 100%
rename from YaeBlog/Directory.Build.targets
rename to src/YaeBlog/Directory.Build.targets
diff --git a/YaeBlog/Dockerfile b/src/YaeBlog/Dockerfile
similarity index 100%
rename from YaeBlog/Dockerfile
rename to src/YaeBlog/Dockerfile
diff --git a/YaeBlog/Exceptions/BlogCommandException.cs b/src/YaeBlog/Exceptions/BlogCommandException.cs
similarity index 100%
rename from YaeBlog/Exceptions/BlogCommandException.cs
rename to src/YaeBlog/Exceptions/BlogCommandException.cs
diff --git a/YaeBlog/Exceptions/BlogFileException.cs b/src/YaeBlog/Exceptions/BlogFileException.cs
similarity index 100%
rename from YaeBlog/Exceptions/BlogFileException.cs
rename to src/YaeBlog/Exceptions/BlogFileException.cs
diff --git a/src/YaeBlog/Exceptions/GiteaFetchException.cs b/src/YaeBlog/Exceptions/GiteaFetchException.cs
new file mode 100644
index 0000000..f795dbf
--- /dev/null
+++ b/src/YaeBlog/Exceptions/GiteaFetchException.cs
@@ -0,0 +1,16 @@
+namespace YaeBlog.Core.Exceptions;
+
+public class GiteaFetchException : Exception
+{
+ public GiteaFetchException() : base()
+ {
+ }
+
+ public GiteaFetchException(string message) : base(message)
+ {
+ }
+
+ public GiteaFetchException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
diff --git a/YaeBlog/Extensions/AngleSharpExtensions.cs b/src/YaeBlog/Extensions/AngleSharpExtensions.cs
similarity index 100%
rename from YaeBlog/Extensions/AngleSharpExtensions.cs
rename to src/YaeBlog/Extensions/AngleSharpExtensions.cs
diff --git a/src/YaeBlog/Extensions/DateOnlyExtensions.cs b/src/YaeBlog/Extensions/DateOnlyExtensions.cs
new file mode 100644
index 0000000..3568d72
--- /dev/null
+++ b/src/YaeBlog/Extensions/DateOnlyExtensions.cs
@@ -0,0 +1,22 @@
+namespace YaeBlog.Extensions;
+
+public static class DateOnlyExtensions
+{
+ extension(DateOnly date)
+ {
+ public static DateOnly Today => DateOnly.FromDateTime(DateTime.Now);
+
+ public DateOnly LastMonday
+ {
+ get
+ {
+ return date.DayOfWeek switch
+ {
+ DayOfWeek.Monday => date,
+ DayOfWeek.Sunday => date.AddDays(-6),
+ _ => date.AddDays(1 - (int)date.DayOfWeek)
+ };
+ }
+ }
+ }
+}
diff --git a/src/YaeBlog/Extensions/ServiceCollectionExtensions.cs b/src/YaeBlog/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..3fefec4
--- /dev/null
+++ b/src/YaeBlog/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,36 @@
+using Markdig;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
+namespace YaeBlog.Extensions;
+
+public static class ServiceCollectionExtensions
+{
+ extension(IServiceCollection collection)
+ {
+ public IServiceCollection AddMarkdig()
+ {
+ MarkdownPipelineBuilder builder = new();
+
+ builder.UseAdvancedExtensions();
+
+ collection.AddSingleton
(_ => builder.Build());
+
+ return collection;
+ }
+
+ public IServiceCollection AddYamlParser()
+ {
+ DeserializerBuilder deserializerBuilder = new();
+ deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
+ deserializerBuilder.IgnoreUnmatchedProperties();
+ collection.AddSingleton(deserializerBuilder.Build());
+
+ SerializerBuilder serializerBuilder = new();
+ serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
+ collection.AddSingleton(serializerBuilder.Build());
+
+ return collection;
+ }
+ }
+}
diff --git a/src/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs b/src/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs
new file mode 100644
index 0000000..e75050a
--- /dev/null
+++ b/src/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs
@@ -0,0 +1,59 @@
+using AngleSharp;
+using YaeBlog.Abstraction;
+using YaeBlog.Services;
+using YaeBlog.Models;
+using YaeBlog.Processors;
+
+namespace YaeBlog.Extensions;
+
+public static class WebApplicationBuilderExtensions
+{
+ extension(WebApplicationBuilder builder)
+ {
+ public WebApplicationBuilder AddYaeBlog()
+ {
+ builder.ConfigureOptions(BlogOptions.OptionName)
+ .ConfigureOptions(GiteaOptions.OptionName);
+
+ builder.Services.AddHttpClient()
+ .AddMarkdig()
+ .AddYamlParser();
+
+ builder.Services.AddSingleton(_ => Configuration.Default)
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddTransient()
+ .AddTransient()
+ .AddTransient()
+ .AddTransient()
+ .AddSingleton();
+
+ return builder;
+ }
+
+ public WebApplicationBuilder AddServer()
+ {
+ builder.Services.AddHostedService();
+
+ return builder;
+ }
+
+ public WebApplicationBuilder AddWatcher()
+ {
+ builder.Services.AddTransient();
+ builder.Services.AddHostedService();
+
+ return builder;
+ }
+
+ private WebApplicationBuilder ConfigureOptions(string optionSectionName) where T : class
+ {
+ builder.Services
+ .AddOptions()
+ .Bind(builder.Configuration.GetSection(optionSectionName))
+ .ValidateDataAnnotations();
+ return builder;
+ }
+ }
+}
diff --git a/YaeBlog/Extensions/WebApplicationExtensions.cs b/src/YaeBlog/Extensions/WebApplicationExtensions.cs
similarity index 100%
rename from YaeBlog/Extensions/WebApplicationExtensions.cs
rename to src/YaeBlog/Extensions/WebApplicationExtensions.cs
diff --git a/YaeBlog/Models/BlogContent.cs b/src/YaeBlog/Models/BlogContent.cs
similarity index 100%
rename from YaeBlog/Models/BlogContent.cs
rename to src/YaeBlog/Models/BlogContent.cs
diff --git a/YaeBlog/Models/BlogContents.cs b/src/YaeBlog/Models/BlogContents.cs
similarity index 100%
rename from YaeBlog/Models/BlogContents.cs
rename to src/YaeBlog/Models/BlogContents.cs
diff --git a/src/YaeBlog/Models/BlogEssay.cs b/src/YaeBlog/Models/BlogEssay.cs
new file mode 100644
index 0000000..44e7283
--- /dev/null
+++ b/src/YaeBlog/Models/BlogEssay.cs
@@ -0,0 +1,34 @@
+namespace YaeBlog.Models;
+
+public record BlogEssay(
+ string Title,
+ string FileName,
+ bool IsDraft,
+ DateTimeOffset PublishTime,
+ DateTimeOffset UpdateTime,
+ string Description,
+ uint WordCount,
+ string ReadTime,
+ List Tags,
+ string HtmlContent) : IComparable
+{
+ public string EssayLink => $"/blog/essays/{FileName}";
+
+ public override string ToString() => $"{Title}-{PublishTime}";
+
+ public int CompareTo(BlogEssay? other)
+ {
+ if (other is null)
+ {
+ return -1;
+ }
+
+ // 草稿文章应当排在前面
+ if (IsDraft != other.IsDraft)
+ {
+ return IsDraft ? -1 : 1;
+ }
+
+ return other.PublishTime.CompareTo(PublishTime);
+ }
+}
diff --git a/YaeBlog/Models/BlogHeadline.cs b/src/YaeBlog/Models/BlogHeadline.cs
similarity index 100%
rename from YaeBlog/Models/BlogHeadline.cs
rename to src/YaeBlog/Models/BlogHeadline.cs
diff --git a/YaeBlog/Models/BlogImageInfo.cs b/src/YaeBlog/Models/BlogImageInfo.cs
similarity index 100%
rename from YaeBlog/Models/BlogImageInfo.cs
rename to src/YaeBlog/Models/BlogImageInfo.cs
diff --git a/src/YaeBlog/Models/BlogOptions.cs b/src/YaeBlog/Models/BlogOptions.cs
new file mode 100644
index 0000000..e6fd993
--- /dev/null
+++ b/src/YaeBlog/Models/BlogOptions.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace YaeBlog.Models;
+
+///
+/// 友链模型类
+///
+public class FriendLink
+{
+ [Required] public required string Name { get; init; }
+
+ [Required] public required string Description { get; init; }
+
+ [Required] public required string Link { get; init; }
+
+ [Required] public required string AvatarImage { get; init; }
+}
+
+public class BlogOptions
+{
+ public const string OptionName = "Blog";
+
+ [Required] public required string Root { get; init; }
+
+ [Required] public required string Announcement { get; init; }
+
+ [Required] public required int StartYear { get; init; }
+
+ [Required] public required List Links { get; init; }
+}
diff --git a/YaeBlog/Models/EssayTag.cs b/src/YaeBlog/Models/EssayTag.cs
similarity index 100%
rename from YaeBlog/Models/EssayTag.cs
rename to src/YaeBlog/Models/EssayTag.cs
diff --git a/src/YaeBlog/Models/GiteaOptions.cs b/src/YaeBlog/Models/GiteaOptions.cs
new file mode 100644
index 0000000..e333452
--- /dev/null
+++ b/src/YaeBlog/Models/GiteaOptions.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace YaeBlog.Models;
+
+public class GiteaOptions
+{
+ public const string OptionName = "Gitea";
+
+ [Required] public required string BaseAddress { get; init; }
+
+ [Required] public required string ApiKey { get; init; }
+
+ [Required] public required string HeatMapUsername { get; init; }
+}
diff --git a/src/YaeBlog/Models/HeatMapItem.cs b/src/YaeBlog/Models/HeatMapItem.cs
new file mode 100644
index 0000000..c61a30e
--- /dev/null
+++ b/src/YaeBlog/Models/HeatMapItem.cs
@@ -0,0 +1,5 @@
+namespace YaeBlog.Models;
+
+public record GitContributionItem(DateOnly Time, long ContributionCount);
+
+public record GitContributionGroupedByWeek(DateOnly Monday, List Contributions);
diff --git a/YaeBlog/Models/MarkdownMetadata.cs b/src/YaeBlog/Models/MarkdownMetadata.cs
similarity index 100%
rename from YaeBlog/Models/MarkdownMetadata.cs
rename to src/YaeBlog/Models/MarkdownMetadata.cs
diff --git a/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs b/src/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs
similarity index 98%
rename from YaeBlog/Processors/EssayStylesPostRenderProcessor.cs
rename to src/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs
index 77a7b4f..ca4d27c 100644
--- a/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs
+++ b/src/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs
@@ -23,7 +23,7 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
BeatifyList(document);
BeatifyInlineCode(document);
- return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
+ return essay with { HtmlContent = document.DocumentElement.OuterHtml };
}
private readonly Dictionary _globalCssStyles = new()
diff --git a/YaeBlog/Processors/HeadlinePostRenderProcessor.cs b/src/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
similarity index 97%
rename from YaeBlog/Processors/HeadlinePostRenderProcessor.cs
rename to src/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
index 955098e..bd2b76c 100644
--- a/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
+++ b/src/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
@@ -67,7 +67,7 @@ public class HeadlinePostRenderProcessor(
logger.LogWarning("Failed to add headline of {}.", essay.FileName);
}
- return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
+ return essay with { HtmlContent = document.DocumentElement.OuterHtml };
}
private static BlogHeadline ParserHeadlineElement(IElement element)
diff --git a/YaeBlog/Processors/ImagePostRenderProcessor.cs b/src/YaeBlog/Processors/ImagePostRenderProcessor.cs
similarity index 85%
rename from YaeBlog/Processors/ImagePostRenderProcessor.cs
rename to src/YaeBlog/Processors/ImagePostRenderProcessor.cs
index 7b02d86..75f3389 100644
--- a/YaeBlog/Processors/ImagePostRenderProcessor.cs
+++ b/src/YaeBlog/Processors/ImagePostRenderProcessor.cs
@@ -7,6 +7,12 @@ using YaeBlog.Models;
namespace YaeBlog.Processors;
+///
+/// 图片地址路径后处理器
+/// 将本地图片地址修改为图片API地址
+///
+///
+///
public class ImagePostRenderProcessor(
ILogger logger,
IOptions options)
@@ -34,7 +40,7 @@ public class ImagePostRenderProcessor(
}
}
- return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
+ return essay with { HtmlContent = html.DocumentElement.OuterHtml };
}
public string Name => nameof(ImagePostRenderProcessor);
@@ -59,7 +65,7 @@ public class ImagePostRenderProcessor(
}
string imageLink = "api/files/" + filename;
- logger.LogDebug("Generate image link '{}' for image file '{}'.",
+ logger.LogDebug("Generate image link '{link}' for image file '{filename}'.",
imageLink, filename);
return imageLink;
diff --git a/YaeBlog/Program.cs b/src/YaeBlog/Program.cs
similarity index 100%
rename from YaeBlog/Program.cs
rename to src/YaeBlog/Program.cs
diff --git a/YaeBlog/Properties/launchSettings.json b/src/YaeBlog/Properties/launchSettings.json
similarity index 100%
rename from YaeBlog/Properties/launchSettings.json
rename to src/YaeBlog/Properties/launchSettings.json
diff --git a/YaeBlog/Services/BlogChangeWatcher.cs b/src/YaeBlog/Services/BlogChangeWatcher.cs
similarity index 100%
rename from YaeBlog/Services/BlogChangeWatcher.cs
rename to src/YaeBlog/Services/BlogChangeWatcher.cs
diff --git a/YaeBlog/Services/BlogHostedService.cs b/src/YaeBlog/Services/BlogHostedService.cs
similarity index 100%
rename from YaeBlog/Services/BlogHostedService.cs
rename to src/YaeBlog/Services/BlogHostedService.cs
diff --git a/YaeBlog/Services/BlogHotReloadService.cs b/src/YaeBlog/Services/BlogHotReloadService.cs
similarity index 100%
rename from YaeBlog/Services/BlogHotReloadService.cs
rename to src/YaeBlog/Services/BlogHotReloadService.cs
diff --git a/YaeBlog/Services/EssayContentService.cs b/src/YaeBlog/Services/EssayContentService.cs
similarity index 100%
rename from YaeBlog/Services/EssayContentService.cs
rename to src/YaeBlog/Services/EssayContentService.cs
diff --git a/YaeBlog/Services/EssayScanService.cs b/src/YaeBlog/Services/EssayScanService.cs
similarity index 92%
rename from YaeBlog/Services/EssayScanService.cs
rename to src/YaeBlog/Services/EssayScanService.cs
index 09b1baa..b35404a 100644
--- a/YaeBlog/Services/EssayScanService.cs
+++ b/src/YaeBlog/Services/EssayScanService.cs
@@ -134,7 +134,8 @@ public partial class EssayScanService : IEssayScanService
}
catch (YamlException e)
{
- _logger.LogWarning("Failed to parser metadata from {name} due to {exception}, skipping", blog.BlogFile.Name, e);
+ _logger.LogWarning("Failed to parser metadata from {name} due to {exception}, skipping",
+ blog.BlogFile.Name, e);
}
}
});
@@ -146,7 +147,6 @@ public partial class EssayScanService : IEssayScanService
private async Task ScanImagePreBlog(DirectoryInfo directory, string blogName, string content)
{
- MatchCollection matchResult = ImagePattern.Matches(content);
DirectoryInfo imageDirectory = new(Path.Combine(directory.FullName, blogName));
Dictionary usedImages = imageDirectory.Exists
@@ -154,10 +154,15 @@ public partial class EssayScanService : IEssayScanService
: [];
List notFoundImages = [];
- foreach (Match match in matchResult)
- {
- string imageName = match.Groups[1].Value;
+ // 同时扫描markdown格式和HTML格式的图片
+ MatchCollection markdownMatchResult = MarkdownImagePattern.Matches(content);
+ MatchCollection htmlMatchResult = HtmlImagePattern.Matches(content);
+ IEnumerable imageNames = from match in markdownMatchResult.Concat(htmlMatchResult)
+ select match.Groups[1].Value;
+
+ foreach (string imageName in imageNames)
+ {
// 判断md文件中的图片名称中是否包含文件夹名称
// 例如 blog-1/image.png 或者 image.png
// 如果不带文件夹名称
@@ -204,7 +209,10 @@ public partial class EssayScanService : IEssayScanService
}
[GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
- private static partial Regex ImagePattern { get; }
+ private static partial Regex MarkdownImagePattern { get; }
+
+ [GeneratedRegex(""" ]*?src\s*=\s*["']([^"']*)["'][^>]*>""")]
+ private static partial Regex HtmlImagePattern { get; }
private DirectoryInfo ValidateRootDirectory()
diff --git a/src/YaeBlog/Services/GitHeatMapService.cs b/src/YaeBlog/Services/GitHeatMapService.cs
new file mode 100644
index 0000000..eae9f55
--- /dev/null
+++ b/src/YaeBlog/Services/GitHeatMapService.cs
@@ -0,0 +1,115 @@
+using DotNext;
+using Microsoft.Extensions.Options;
+using YaeBlog.Extensions;
+using YaeBlog.Models;
+
+namespace YaeBlog.Services;
+
+public sealed class GitHeapMapService(IServiceProvider serviceProvider, IOptions giteaOptions,
+ ILogger logger)
+{
+ ///
+ /// 存储贡献列表
+ /// 贡献列表采用懒加载和缓存机制,一天之内只请求一次Gitea服务器获得数据并缓存
+ ///
+ private List _gitContributionsGroupedByWeek = [];
+
+ ///
+ /// 最后一次更新贡献列表的时间
+ ///
+ private DateOnly _updateTime = DateOnly.MinValue;
+
+ public async Task> GetGitContributionGroupedByWeek()
+ {
+ DateOnly today = DateOnly.FromDateTime(DateTimeOffset.Now.DateTime);
+ if (_updateTime == today)
+ {
+ logger.LogDebug("Git contribution grouped by week cache is hit.");
+ return _gitContributionsGroupedByWeek;
+ }
+
+ // 今天尚未更新
+ // 更新一下
+ GiteaFetchService giteaFetchService = serviceProvider.GetRequiredService();
+ Result> r =
+ await giteaFetchService.FetchGiteaContributions(giteaOptions.Value.HeatMapUsername);
+
+ if (!r.TryGet(out List? items))
+ {
+ logger.LogError("Failed to fetch heatmap data: {}", r.Error);
+ return _gitContributionsGroupedByWeek;
+ }
+
+ // The contribution is not grouped by day, so group them.
+ IEnumerable groupedItems = items
+ .GroupBy(i => i.Time)
+ .Select(group => new GitContributionItem(group.Key,
+ group.Select(i => i.ContributionCount).Sum()));
+
+ List result = new(52);
+
+ // Consider the input data is in order.
+ // Start should be one year ago.
+ GitContributionGroupedByWeek groupedContribution = new(DateOnly.Today.AddDays(-365 - 7).LastMonday, []);
+ logger.LogDebug("Create new item group by week {}.", groupedContribution.Monday);
+
+ foreach ((DateOnly date, long contributions) in groupedItems)
+ {
+ DateOnly mondayOfItem = date.LastMonday;
+ logger.LogDebug("Current date of item: {item}, monday is {monday}", date, mondayOfItem);
+
+ // If current item is in the same week of last item.
+ if (mondayOfItem == groupedContribution.Monday)
+ {
+ // Fill the spacing of empty days with 0 contribution.
+ FillSpacing(groupedContribution, date);
+
+ groupedContribution.Contributions.Add(new GitContributionItem(date, contributions));
+ continue;
+ }
+
+ // Current time is in the next (or much more) week of last item.
+ // Fill the spacing, including the last week inner spacing and outer spacing.
+ while (groupedContribution.Monday < mondayOfItem)
+ {
+ FillSpacing(groupedContribution, date);
+ result.Add(groupedContribution);
+ groupedContribution = new GitContributionGroupedByWeek(groupedContribution.Monday.AddDays(7), []);
+ logger.LogDebug("Create new item group by week {}.", groupedContribution.Monday);
+ }
+
+ // Now, the inner spacing of one week.
+ FillSpacing(groupedContribution, date);
+ groupedContribution.Contributions.Add(new GitContributionItem(date, contributions));
+ }
+
+ // Not fill the last item and add directly.
+ result.Add(groupedContribution);
+
+ _gitContributionsGroupedByWeek = result;
+ _updateTime = DateOnly.Today;
+
+ return _gitContributionsGroupedByWeek;
+ }
+
+ private static void FillSpacing(GitContributionGroupedByWeek contribution, in DateOnly date)
+ {
+ if (contribution.Monday == date)
+ {
+ return;
+ }
+
+ if (contribution.Contributions.Count == 0)
+ {
+ contribution.Contributions.Add(new GitContributionItem(contribution.Monday, 0));
+ }
+
+ DateOnly lastDate = contribution.Contributions.Last().Time;
+ // The day in one week is 7, so th count of items of one week should not bigger than 7.
+ while (contribution.Contributions.Count < 7 && lastDate < date.AddDays(-1))
+ {
+ lastDate = lastDate.AddDays(1);
+ contribution.Contributions.Add(new GitContributionItem(lastDate, 0));
+ }
+ }
+}
diff --git a/src/YaeBlog/Services/GiteaFetchService.cs b/src/YaeBlog/Services/GiteaFetchService.cs
new file mode 100644
index 0000000..18ab068
--- /dev/null
+++ b/src/YaeBlog/Services/GiteaFetchService.cs
@@ -0,0 +1,63 @@
+using System.Net.Http.Headers;
+using System.Text.Json;
+using DotNext;
+using Microsoft.Extensions.Options;
+using YaeBlog.Core.Exceptions;
+using YaeBlog.Models;
+
+namespace YaeBlog.Services;
+
+public sealed class GiteaFetchService
+{
+ private readonly HttpClient _httpClient;
+
+ private static readonly JsonSerializerOptions s_serializerOptions = new()
+ {
+ PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ RespectRequiredConstructorParameters = true, RespectNullableAnnotations = true
+ };
+
+ ///
+ /// For test only.
+ ///
+ internal GiteaFetchService(IOptions giteaOptions, HttpClient httpClient)
+ {
+ _httpClient = httpClient;
+
+ _httpClient.BaseAddress = new Uri(giteaOptions.Value.BaseAddress);
+ _httpClient.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", giteaOptions.Value.ApiKey);
+ }
+
+ public GiteaFetchService(IOptions giteaOptions, IHttpClientFactory httpClientFactory) : this(
+ giteaOptions, httpClientFactory.CreateClient())
+ {
+ }
+
+ private record UserHeatmapData(long Contributions, long Timestamp);
+
+ public async Task>> FetchGiteaContributions(string username)
+ {
+ try
+ {
+ List? data =
+ await _httpClient.GetFromJsonAsync>($"users/{username}/heatmap",
+ s_serializerOptions);
+
+ if (data is null or { Count: 0 })
+ {
+ return Result.FromException>(
+ new GiteaFetchException("Failed to fetch valid data."));
+ }
+
+ return Result.FromValue(data.Select(i =>
+ new GitContributionItem(DateOnly.FromDateTime(DateTimeOffset.FromUnixTimeSeconds(i.Timestamp).DateTime),
+ i.Contributions)).ToList());
+ }
+ catch (HttpRequestException exception)
+ {
+ return Result.FromException>(new GiteaFetchException("Failed to fetch.",
+ exception));
+ }
+ }
+}
diff --git a/YaeBlog/Services/ImageCompressService.cs b/src/YaeBlog/Services/ImageCompressService.cs
similarity index 100%
rename from YaeBlog/Services/ImageCompressService.cs
rename to src/YaeBlog/Services/ImageCompressService.cs
diff --git a/YaeBlog/Services/MarkdownWordCounter.cs b/src/YaeBlog/Services/MarkdownWordCounter.cs
similarity index 100%
rename from YaeBlog/Services/MarkdownWordCounter.cs
rename to src/YaeBlog/Services/MarkdownWordCounter.cs
diff --git a/YaeBlog/Services/RendererService.cs b/src/YaeBlog/Services/RendererService.cs
similarity index 83%
rename from YaeBlog/Services/RendererService.cs
rename to src/YaeBlog/Services/RendererService.cs
index 95ab57e..a4054d9 100644
--- a/YaeBlog/Services/RendererService.cs
+++ b/src/YaeBlog/Services/RendererService.cs
@@ -34,9 +34,9 @@ public sealed partial class RendererService(
}
IEnumerable preProcessedContents = await PreProcess(posts);
+ ConcurrentBag essays = [];
- List essays = [];
- foreach (BlogContent content in preProcessedContents)
+ Parallel.ForEach(preProcessedContents, content =>
{
(uint wordCount, string readTime) = GetWordCount(content);
DateTimeOffset publishDate = content.Metadata.Date is null
@@ -46,39 +46,20 @@ public sealed partial class RendererService(
DateTimeOffset updateTime = content.Metadata.UpdateTime is null
? publishDate
: DateTimeOffset.Parse(content.Metadata.UpdateTime);
+ string description = GetDescription(content);
+ List tags = content.Metadata.Tags ?? [];
- BlogEssay essay = new()
- {
- Title = content.Metadata.Title ?? content.BlogName,
- FileName = content.BlogName,
- IsDraft = content.IsDraft,
- Description = GetDescription(content),
- WordCount = wordCount,
- ReadTime = readTime,
- PublishTime = publishDate,
- UpdateTime = updateTime,
- HtmlContent = content.Content
- };
+ string originalHtml = Markdown.ToHtml(content.Content, markdownPipeline);
- if (content.Metadata.Tags is not null)
- {
- essay.Tags.AddRange(content.Metadata.Tags);
- }
+ BlogEssay essay = new(
+ content.Metadata.Title ?? content.BlogName, content.BlogName, content.IsDraft, publishDate, updateTime,
+ description, wordCount, readTime, tags, originalHtml);
+ logger.LogDebug("Render essay: {}", essay);
essays.Add(essay);
- }
-
- ConcurrentBag postProcessEssays = [];
- Parallel.ForEach(essays, essay =>
- {
- BlogEssay newEssay =
- essay.WithNewHtmlContent(Markdown.ToHtml(essay.HtmlContent, markdownPipeline));
-
- postProcessEssays.Add(newEssay);
- logger.LogDebug("Render markdown file {}.", newEssay);
});
- IEnumerable postProcessedEssays = await PostProcess(postProcessEssays);
+ IEnumerable postProcessedEssays = await PostProcess(essays);
foreach (BlogEssay essay in postProcessedEssays)
{
diff --git a/YaeBlog/YaeBlog.csproj b/src/YaeBlog/YaeBlog.csproj
similarity index 50%
rename from YaeBlog/YaeBlog.csproj
rename to src/YaeBlog/YaeBlog.csproj
index 642cf90..d25927b 100644
--- a/YaeBlog/YaeBlog.csproj
+++ b/src/YaeBlog/YaeBlog.csproj
@@ -1,13 +1,22 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18,6 +27,6 @@
pnpm install
- pwsh build.ps1 tailwind
+ pwsh ../../build.ps1 tailwind
diff --git a/src/YaeBlog/appsettings.json b/src/YaeBlog/appsettings.json
new file mode 100644
index 0000000..f55e6fd
--- /dev/null
+++ b/src/YaeBlog/appsettings.json
@@ -0,0 +1,44 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "System.Net.Http.HttpClient": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "Tailwind": {
+ "InputFile": "wwwroot/input.css",
+ "OutputFile": "wwwroot/output.css"
+ },
+ "Blog": {
+ "Root": "../../source",
+ "Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。",
+ "StartYear": 2021,
+ "Links": [
+ {
+ "Name": "Ichirinko",
+ "Description": "这是个大哥",
+ "Link": "https://ichirinko.top",
+ "AvatarImage": "https://ichirinko-blog-img-1.oss-cn-shenzhen.aliyuncs.com/Pic_res/img/202209122110798.png"
+ },
+ {
+ "Name": "不会写程序的晨旭",
+ "Description": "一个普通大学生",
+ "Link": "https://chenxutalk.top",
+ "AvatarImage": "https://www.chenxutalk.top/img/photo.png"
+ },
+ {
+ "Name": "万木长风",
+ "Description": "世界渲染中...",
+ "Link": "https://ryohai.fun",
+ "AvatarImage": "https://ryohai.fun/static/favicons/favicon-32x32.png"
+ }
+ ]
+ },
+ "Gitea": {
+ "BaseAddress": "https://git.rrricardo.top/api/v1/",
+ "ApiKey": "7e33617e5d084199332fceec3e0cb04c6ddced55",
+ "HeatMapUsername": "jackfiled"
+ }
+}
diff --git a/YaeBlog/docker-compose.yaml b/src/YaeBlog/docker-compose.yaml
similarity index 100%
rename from YaeBlog/docker-compose.yaml
rename to src/YaeBlog/docker-compose.yaml
diff --git a/YaeBlog/package.json b/src/YaeBlog/package.json
similarity index 100%
rename from YaeBlog/package.json
rename to src/YaeBlog/package.json
diff --git a/YaeBlog/pnpm-lock.yaml b/src/YaeBlog/pnpm-lock.yaml
similarity index 100%
rename from YaeBlog/pnpm-lock.yaml
rename to src/YaeBlog/pnpm-lock.yaml
diff --git a/YaeBlog/wwwroot/fonts/.gitattributes b/src/YaeBlog/wwwroot/fonts/.gitattributes
similarity index 100%
rename from YaeBlog/wwwroot/fonts/.gitattributes
rename to src/YaeBlog/wwwroot/fonts/.gitattributes
diff --git a/src/YaeBlog/wwwroot/fonts/fa-brands-400.woff2 b/src/YaeBlog/wwwroot/fonts/fa-brands-400.woff2
new file mode 100644
index 0000000..6af4c60
--- /dev/null
+++ b/src/YaeBlog/wwwroot/fonts/fa-brands-400.woff2
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:061dd5c333459ea42dba764617793fd6ea2d316b7ab644f157e4d2354dac02af
+size 101224
diff --git a/src/YaeBlog/wwwroot/fonts/fa-regular-400.woff2 b/src/YaeBlog/wwwroot/fonts/fa-regular-400.woff2
new file mode 100644
index 0000000..296655c
--- /dev/null
+++ b/src/YaeBlog/wwwroot/fonts/fa-regular-400.woff2
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:81159a6b36876a5545555ae689144f074e2fc802d57d36f2c21bc6f3a12f4e48
+size 18988
diff --git a/src/YaeBlog/wwwroot/fonts/fa-solid-900.woff2 b/src/YaeBlog/wwwroot/fonts/fa-solid-900.woff2
new file mode 100644
index 0000000..9b80394
--- /dev/null
+++ b/src/YaeBlog/wwwroot/fonts/fa-solid-900.woff2
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bdd7887ef769948024a5cc37a018f19da6a9b355b4a09973836115e0d31ead55
+size 113152
diff --git a/YaeBlog/wwwroot/images/alipay-code.jpeg b/src/YaeBlog/wwwroot/images/alipay-code.jpeg
similarity index 100%
rename from YaeBlog/wwwroot/images/alipay-code.jpeg
rename to src/YaeBlog/wwwroot/images/alipay-code.jpeg
diff --git a/YaeBlog/wwwroot/images/avatar.png b/src/YaeBlog/wwwroot/images/avatar.png
similarity index 100%
rename from YaeBlog/wwwroot/images/avatar.png
rename to src/YaeBlog/wwwroot/images/avatar.png
diff --git a/YaeBlog/wwwroot/images/favicon.ico b/src/YaeBlog/wwwroot/images/favicon.ico
similarity index 100%
rename from YaeBlog/wwwroot/images/favicon.ico
rename to src/YaeBlog/wwwroot/images/favicon.ico
diff --git a/YaeBlog/wwwroot/images/wechat-code.jpeg b/src/YaeBlog/wwwroot/images/wechat-code.jpeg
similarity index 100%
rename from YaeBlog/wwwroot/images/wechat-code.jpeg
rename to src/YaeBlog/wwwroot/images/wechat-code.jpeg
diff --git a/src/YaeBlog/wwwroot/images/xiaohongshu-seeklogo.svg b/src/YaeBlog/wwwroot/images/xiaohongshu-seeklogo.svg
new file mode 100644
index 0000000..3a2fb33
--- /dev/null
+++ b/src/YaeBlog/wwwroot/images/xiaohongshu-seeklogo.svg
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/YaeBlog/wwwroot/tailwind.css b/src/YaeBlog/wwwroot/tailwind.css
similarity index 100%
rename from YaeBlog/wwwroot/tailwind.css
rename to src/YaeBlog/wwwroot/tailwind.css
diff --git a/third-party/BlazorSvgComponents b/third-party/BlazorSvgComponents
new file mode 160000
index 0000000..909448d
--- /dev/null
+++ b/third-party/BlazorSvgComponents
@@ -0,0 +1 @@
+Subproject commit 909448d9f5ad274b6e0b61355381a45e63bbc735