From a483ddc6711ce6f23686ba2d709587927adaceef Mon Sep 17 00:00:00 2001 From: jackfiled Date: Mon, 29 Jul 2024 22:32:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BE=8E=E5=8C=96=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.rrricardo.top/jackfiled/YaeBlog/pulls/3 --- .../Abstractions/IEssayContentService.cs | 13 ++ .../Abstractions/ITableOfContentService.cs | 8 + .../WebApplicationBuilderExtensions.cs | 13 +- .../Extensions/WebApplicationExtensions.cs | 7 +- YaeBlog.Core/Models/BlogEssay.cs | 3 + YaeBlog.Core/Models/BlogHeadline.cs | 10 ++ YaeBlog.Core/Models/BlogOptions.cs | 20 +-- YaeBlog.Core/Models/EssayTag.cs | 16 ++ .../CodeBlockPostRenderProcessor.cs | 29 ++++ .../Processors/HeadlinePostRenderProcessor.cs | 105 ++++++++++++ .../Processors/ImagePostRenderProcessor.cs | 2 +- .../Processors/TablePostRenderProcessor.cs | 34 ++++ YaeBlog.Core/Services/EssayContentService.cs | 63 ++++---- YaeBlog.Core/Services/RendererService.cs | 48 ++++-- .../Services/TableOfContentService.cs | 20 +++ YaeBlog/Components/App.razor | 4 + YaeBlog/Components/BlogInformationCard.razor | 25 ++- YaeBlog/Components/LicenseDisclaimer.razor | 36 +++++ .../Components/LicenseDisclaimer.razor.css | 2 + YaeBlog/Layout/BlogLayout.razor | 52 +++--- YaeBlog/Pages/About.razor | 150 ++++++++++++------ YaeBlog/Pages/About.razor.css | 8 + YaeBlog/Pages/Archives.razor | 11 +- YaeBlog/Pages/BlogIndex.razor | 14 +- YaeBlog/Pages/Essays.razor | 88 +++++++++- YaeBlog/Pages/Index.razor | 41 ++--- YaeBlog/Pages/NotFound.razor | 4 + YaeBlog/Pages/Tags.razor | 39 ++++- YaeBlog/Program.cs | 2 +- YaeBlog/appsettings.json | 10 -- YaeBlog/source/2021-final.md | 2 - YaeBlog/source/2022-final.md | 2 - YaeBlog/source/2022-summer-vacation.md | 3 - YaeBlog/source/big-homework.md | 2 - YaeBlog/source/build-blog-record.md | 6 +- YaeBlog/source/c-include-problems.md | 1 - YaeBlog/source/compile-mediapipe.md | 3 +- YaeBlog/source/daily-linux-0.md | 3 - YaeBlog/source/daily-linux-1.md | 2 - YaeBlog/source/daily-linux-2.md | 2 - YaeBlog/source/daily-linux-3.md | 2 - YaeBlog/source/dotnet-come-to-go.md | 48 ------ YaeBlog/source/environment-setting.md | 2 - YaeBlog/source/genshin-gacha-1.md | 2 +- YaeBlog/source/install-pytorch.md | 4 +- YaeBlog/source/laptop-for-computer.md | 2 - YaeBlog/source/linux-genshin-cloud.md | 3 - YaeBlog/source/minecraft-wayland.md | 4 +- YaeBlog/source/post-calendar.md | 54 ------- YaeBlog/source/post-calendar/Activity.png | 3 - YaeBlog/source/post-calendar/Calendar.png | 3 - YaeBlog/source/post-calendar/DDL.png | 3 - YaeBlog/source/program-design-introduction.md | 4 +- YaeBlog/source/qt-learning.md | 2 - YaeBlog/source/question-in-install-vs-2019.md | 1 - .../source/spring-boot-custom-authorize.md | 3 +- YaeBlog/source/using-vpn-elegant.md | 2 +- YaeBlog/source/vscode-in-browser.md | 1 - YaeBlog/source/wsl-setup-csapp.md | 3 - YaeBlog/wwwroot/clipboard.min.js | 7 + YaeBlog/wwwroot/globals.css | 95 ++++++++--- YaeBlog/wwwroot/themes.js | 8 - 62 files changed, 771 insertions(+), 388 deletions(-) create mode 100644 YaeBlog.Core/Abstractions/IEssayContentService.cs create mode 100644 YaeBlog.Core/Abstractions/ITableOfContentService.cs create mode 100644 YaeBlog.Core/Models/BlogHeadline.cs create mode 100644 YaeBlog.Core/Models/EssayTag.cs create mode 100644 YaeBlog.Core/Processors/CodeBlockPostRenderProcessor.cs create mode 100644 YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs create mode 100644 YaeBlog.Core/Processors/TablePostRenderProcessor.cs create mode 100644 YaeBlog.Core/Services/TableOfContentService.cs create mode 100644 YaeBlog/Components/LicenseDisclaimer.razor create mode 100644 YaeBlog/Components/LicenseDisclaimer.razor.css delete mode 100644 YaeBlog/source/dotnet-come-to-go.md delete mode 100644 YaeBlog/source/post-calendar.md delete mode 100644 YaeBlog/source/post-calendar/Activity.png delete mode 100644 YaeBlog/source/post-calendar/Calendar.png delete mode 100644 YaeBlog/source/post-calendar/DDL.png create mode 100644 YaeBlog/wwwroot/clipboard.min.js delete mode 100644 YaeBlog/wwwroot/themes.js diff --git a/YaeBlog.Core/Abstractions/IEssayContentService.cs b/YaeBlog.Core/Abstractions/IEssayContentService.cs new file mode 100644 index 0000000..3fbbb42 --- /dev/null +++ b/YaeBlog.Core/Abstractions/IEssayContentService.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Abstractions; + +public interface IEssayContentService +{ + public IReadOnlyDictionary Essays { get; } + + public IReadOnlyDictionary> Tags { get; } + + public bool SearchByUrlEncodedTag(string tag,[NotNullWhen(true)] out List? result); +} diff --git a/YaeBlog.Core/Abstractions/ITableOfContentService.cs b/YaeBlog.Core/Abstractions/ITableOfContentService.cs new file mode 100644 index 0000000..08b997c --- /dev/null +++ b/YaeBlog.Core/Abstractions/ITableOfContentService.cs @@ -0,0 +1,8 @@ +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Abstractions; + +public interface ITableOfContentService +{ + public IReadOnlyDictionary Headlines { get; } +} diff --git a/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs b/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs index 6ed6680..d469a33 100644 --- a/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs +++ b/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs @@ -1,6 +1,8 @@ -using Microsoft.AspNetCore.Builder; +using AngleSharp; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using YaeBlog.Core.Abstractions; using YaeBlog.Core.Models; using YaeBlog.Core.Processors; using YaeBlog.Core.Services; @@ -17,10 +19,19 @@ public static class WebApplicationBuilderExtensions builder.Services.AddMarkdig(); builder.Services.AddYamlParser(); + builder.Services.AddSingleton(_ => Configuration.Default); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(provider => + provider.GetRequiredService()); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(provider => + provider.GetRequiredService()); builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Services.AddTransient(provider => provider.GetRequiredService>().Value); diff --git a/YaeBlog.Core/Extensions/WebApplicationExtensions.cs b/YaeBlog.Core/Extensions/WebApplicationExtensions.cs index f541f4a..563395c 100644 --- a/YaeBlog.Core/Extensions/WebApplicationExtensions.cs +++ b/YaeBlog.Core/Extensions/WebApplicationExtensions.cs @@ -8,11 +8,12 @@ namespace YaeBlog.Core.Extensions; public static class WebApplicationExtensions { - public static WebApplication UseMiddleRenderProcessors(this WebApplication application) + public static void UseYaeBlog(this WebApplication application) { application.UsePostRenderProcessor(); - - return application; + application.UsePostRenderProcessor(); + application.UsePostRenderProcessor(); + application.UsePostRenderProcessor(); } private static void UsePreRenderProcessor(this WebApplication application) where T : IPreRenderProcessor diff --git a/YaeBlog.Core/Models/BlogEssay.cs b/YaeBlog.Core/Models/BlogEssay.cs index 5e41f02..b8e60b6 100644 --- a/YaeBlog.Core/Models/BlogEssay.cs +++ b/YaeBlog.Core/Models/BlogEssay.cs @@ -12,6 +12,8 @@ public class BlogEssay : IComparable public required uint WordCount { get; init; } + public required string ReadTime { get; init; } + public List Tags { get; } = []; public required string HtmlContent { get; init; } @@ -25,6 +27,7 @@ public class BlogEssay : IComparable PublishTime = PublishTime, Description = Description, WordCount = WordCount, + ReadTime = ReadTime, HtmlContent = newHtmlContent }; essay.Tags.AddRange(Tags); diff --git a/YaeBlog.Core/Models/BlogHeadline.cs b/YaeBlog.Core/Models/BlogHeadline.cs new file mode 100644 index 0000000..f328290 --- /dev/null +++ b/YaeBlog.Core/Models/BlogHeadline.cs @@ -0,0 +1,10 @@ +namespace YaeBlog.Core.Models; + +public class BlogHeadline(string title, string selectorId) +{ + public string Title { get; } = title; + + public string SelectorId { get; set; } = selectorId; + + public List Children { get; } = []; +} diff --git a/YaeBlog.Core/Models/BlogOptions.cs b/YaeBlog.Core/Models/BlogOptions.cs index 69c0858..246f11d 100644 --- a/YaeBlog.Core/Models/BlogOptions.cs +++ b/YaeBlog.Core/Models/BlogOptions.cs @@ -10,10 +10,8 @@ public class BlogOptions public required string Root { get; set; } /// - /// 博客作者 + /// 博客正文的广而告之 /// - public required string Author { get; set; } - public required string Announcement { get; set; } /// @@ -22,21 +20,7 @@ public class BlogOptions public required int StartYear { get; set; } /// - /// 博客起始页面的背景图片 + /// 博客的友链 /// - public required string BannerImage { get; set; } - - /// - /// 文章页面的背景图片 - /// - public required string EssayImage { get; set; } - - /// - /// 博客底部是否显示ICP备案信息 - /// - public string? RegisterInformation { get; set; } - - public required AboutInfo About { get; set; } - public required List Links { get; set; } } diff --git a/YaeBlog.Core/Models/EssayTag.cs b/YaeBlog.Core/Models/EssayTag.cs new file mode 100644 index 0000000..6528943 --- /dev/null +++ b/YaeBlog.Core/Models/EssayTag.cs @@ -0,0 +1,16 @@ +using System.Text.Encodings.Web; + +namespace YaeBlog.Core.Models; + +public class EssayTag(string tagName) : IEquatable +{ + 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(); +} diff --git a/YaeBlog.Core/Processors/CodeBlockPostRenderProcessor.cs b/YaeBlog.Core/Processors/CodeBlockPostRenderProcessor.cs new file mode 100644 index 0000000..217aa38 --- /dev/null +++ b/YaeBlog.Core/Processors/CodeBlockPostRenderProcessor.cs @@ -0,0 +1,29 @@ +using AngleSharp; +using AngleSharp.Dom; +using YaeBlog.Core.Abstractions; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Processors; + +public class CodeBlockPostRenderProcessor : IPostRenderProcessor +{ + public async Task ProcessAsync(BlogEssay essay) + { + BrowsingContext context = new(Configuration.Default); + IDocument document = await context.OpenAsync( + req => req.Content(essay.HtmlContent)); + + IEnumerable preElements = from e in document.All + where e.LocalName == "pre" + select e; + + foreach (IElement element in preElements) + { + element.ClassList.Add("p-3 text-bg-secondary rounded-1"); + } + + return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml); + } + + public string Name => nameof(CodeBlockPostRenderProcessor); +} diff --git a/YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs b/YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs new file mode 100644 index 0000000..ddfed50 --- /dev/null +++ b/YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs @@ -0,0 +1,105 @@ +using AngleSharp; +using AngleSharp.Dom; +using YaeBlog.Core.Abstractions; +using YaeBlog.Core.Models; +using YaeBlog.Core.Services; + +namespace YaeBlog.Core.Processors; + +public class HeadlinePostRenderProcessor( + IConfiguration angleConfiguration, + TableOfContentService tableOfContentService) : IPostRenderProcessor +{ + public async Task ProcessAsync(BlogEssay essay) + { + BrowsingContext browsingContext = new(angleConfiguration); + IDocument document = await browsingContext.OpenAsync(req => req.Content(essay.HtmlContent)); + + IEnumerable elements = from item in document.All + where item.LocalName is "h2" or "h3" or "h4" + select item; + + BlogHeadline topHeadline = new(essay.Title, "#title"); + List level2List = []; + List level3List = []; + List level4List = []; + + foreach (IElement element in elements) + { + switch (element.LocalName) + { + case "h2": + { + FindParentHeadline(topHeadline, level2List, level3List).Children.AddRange(level4List); + level4List.Clear(); + FindParentHeadline(topHeadline, level2List).Children.AddRange(level3List); + level3List.Clear(); + + BlogHeadline headline = ParserHeadlineElement(element); + level2List.Add(headline); + break; + } + case "h3": + { + FindParentHeadline(topHeadline, level2List, level3List).Children.AddRange(level4List); + level4List.Clear(); + + BlogHeadline headline = ParserHeadlineElement(element); + level3List.Add(headline); + break; + } + case "h4": + { + BlogHeadline headline = ParserHeadlineElement(element); + level4List.Add(headline); + break; + } + } + } + + // 太抽象了((( + FindParentHeadline(topHeadline, level2List, level3List).Children.AddRange(level4List); + FindParentHeadline(topHeadline, level2List).Children.AddRange(level3List); + topHeadline.Children.AddRange(level2List); + + tableOfContentService.AddHeadline(essay.FileName, topHeadline); + + return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml); + } + + private static BlogHeadline ParserHeadlineElement(IElement element) + { + element.Id ??= element.TextContent; + return new BlogHeadline(element.TextContent, element.Id); + } + + /// + /// 找到h4标题的父级标题 + /// + /// + /// + /// + /// + private static BlogHeadline FindParentHeadline(BlogHeadline topHeadline, List level2, + List level3) + { + BlogHeadline? result = level3.LastOrDefault(); + if (result is not null) + { + return result; + } + + return level2.LastOrDefault() ?? topHeadline; + } + + /// + /// 找到h3标题的父级标题 + /// + /// + /// + /// + private static BlogHeadline FindParentHeadline(BlogHeadline topHeadline, List level2) => + FindParentHeadline(topHeadline, level2, []); + + public string Name => nameof(HeadlinePostRenderProcessor); +} diff --git a/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs b/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs index d4a61a8..0592899 100644 --- a/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs +++ b/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs @@ -38,7 +38,7 @@ public class ImagePostRenderProcessor(ILogger logger, return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml); } - public string Name => "ImagePostRenderProcessor"; + public string Name => nameof(ImagePostRenderProcessor); private string GenerateImageLink(string filename, string essayFilename) { diff --git a/YaeBlog.Core/Processors/TablePostRenderProcessor.cs b/YaeBlog.Core/Processors/TablePostRenderProcessor.cs new file mode 100644 index 0000000..e2c3444 --- /dev/null +++ b/YaeBlog.Core/Processors/TablePostRenderProcessor.cs @@ -0,0 +1,34 @@ +using AngleSharp; +using AngleSharp.Dom; +using AngleSharp.Html.Dom; +using YaeBlog.Core.Abstractions; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Processors; + +public class TablePostRenderProcessor: IPostRenderProcessor +{ + public async Task ProcessAsync(BlogEssay essay) + { + BrowsingContext browsingContext = new(Configuration.Default); + IDocument document = await browsingContext.OpenAsync( + req => req.Content(essay.HtmlContent)); + + IEnumerable tableElements = from item in document.All + where item.LocalName == "table" + select item as IHtmlTableElement; + + foreach (IHtmlTableElement element in tableElements) + { + IHtmlDivElement divElement = document.CreateElement(); + divElement.InnerHtml = element.OuterHtml; + divElement.ClassList.Add("py-2", "table-wrapper"); + + element.Replace(divElement); + } + + return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml); + } + + public string Name => nameof(TablePostRenderProcessor); +} diff --git a/YaeBlog.Core/Services/EssayContentService.cs b/YaeBlog.Core/Services/EssayContentService.cs index 2ddc98c..aa800f2 100644 --- a/YaeBlog.Core/Services/EssayContentService.cs +++ b/YaeBlog.Core/Services/EssayContentService.cs @@ -1,57 +1,48 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using YaeBlog.Core.Abstractions; using YaeBlog.Core.Models; namespace YaeBlog.Core.Services; -public class EssayContentService +public class EssayContentService : IEssayContentService { private readonly ConcurrentDictionary _essays = new(); - private readonly Dictionary> _tags = []; + private readonly Dictionary> _tags = []; - public bool TryGet(string key, out BlogEssay? essay) - => _essays.TryGetValue(key, out essay); + public bool TryAdd(BlogEssay essay) => _essays.TryAdd(essay.FileName, essay); - public bool TryAdd(string key, BlogEssay essay) => _essays.TryAdd(key, essay); + public IReadOnlyDictionary Essays => _essays; - public IDictionary Essays => _essays; + public IReadOnlyDictionary> Tags => _tags; public void RefreshTags() { - foreach (BlogEssay essay in _essays.Values) - { - foreach (string tag in essay.Tags) - { - if (_tags.TryGetValue(tag, out var list)) - { - list.Add(essay); - } - else - { - _tags[tag] = [essay]; - } - } - } + _tags.Clear(); - foreach (KeyValuePair> pair in _tags) - { - pair.Value.Sort(); - } + foreach (BlogEssay essay in _essays.Values) + { + foreach (EssayTag essayTag in essay.Tags.Select(tag => new EssayTag(tag))) + { + if (_tags.TryGetValue(essayTag, out List? essays)) + { + essays.Add(essay); + } + else + { + _tags.Add(essayTag, [essay]); + } + } + } } - public IEnumerable> Tags => from item in _tags - orderby item.Value.Count descending - select KeyValuePair.Create(item.Key, item.Value.Count); - - public int TagCount => _tags.Count; - - public IEnumerable GetTag(string tag) + public bool SearchByUrlEncodedTag(string tag, [NotNullWhen(true)] out List? result) { - if (_tags.TryGetValue(tag, out var list)) - { - return list; - } + result = (from item in _tags + where item.Key.UrlEncodedTagName == tag + select item.Value).FirstOrDefault(); - throw new KeyNotFoundException("Selected tag not found."); + return result is not null; } } diff --git a/YaeBlog.Core/Services/RendererService.cs b/YaeBlog.Core/Services/RendererService.cs index a4359ea..3195efb 100644 --- a/YaeBlog.Core/Services/RendererService.cs +++ b/YaeBlog.Core/Services/RendererService.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Text; +using System.Text.RegularExpressions; using Markdig; using Microsoft.Extensions.Logging; using YaeBlog.Core.Abstractions; @@ -11,7 +12,8 @@ using YamlDotNet.Serialization; namespace YaeBlog.Core.Services; -public class RendererService(ILogger logger, +public partial class RendererService( + ILogger logger, EssayScanService essayScanService, MarkdownPipeline markdownPipeline, IDeserializer yamlDeserializer, @@ -37,12 +39,14 @@ public class RendererService(ILogger logger, foreach (BlogContent content in preProcessedContents) { MarkdownMetadata? metadata = TryParseMetadata(content); + uint wordCount = GetWordCount(content); BlogEssay essay = new() { Title = metadata?.Title ?? content.FileName, FileName = content.FileName, Description = GetDescription(content), - WordCount = GetWordCount(content), + WordCount = wordCount, + ReadTime = CalculateReadTime(wordCount), PublishTime = metadata?.Date ?? DateTime.Now, HtmlContent = content.FileContent }; @@ -51,6 +55,7 @@ public class RendererService(ILogger logger, { essay.Tags.AddRange(metadata.Tags); } + essays.Add(essay); } }); @@ -125,7 +130,7 @@ public class RendererService(ILogger logger, essay = await processor.ProcessAsync(essay); } - if (!essayContentService.TryAdd(essay.FileName, essay)) + if (!essayContentService.TryAdd(essay)) { throw new BlogFileException( $"There are two essays with the same name: '{essay.FileName}'."); @@ -172,30 +177,34 @@ public class RendererService(ILogger logger, } } + [GeneratedRegex(@"(?"; int pos = content.FileContent.IndexOf(delimiter, StringComparison.Ordinal); - StringBuilder builder = new(); + bool breakSentence = false; if (pos == -1) { // 自动截取前50个字符 pos = content.FileContent.Length < 50 ? content.FileContent.Length : 50; + breakSentence = true; } - for (int i = 0; i < pos; i++) + string rawContent = content.FileContent[..pos]; + MatchCollection matches = DescriptionPattern().Matches(rawContent); + + StringBuilder builder = new(); + foreach (Match match in matches) { - char c = content.FileContent[i]; + builder.Append(match.Value); + } - if (char.IsControl(c) || char.IsSymbol(c) || - char.IsSeparator(c) || char.IsPunctuation(c) || - char.IsAsciiLetter(c)) - { - continue; - } - - builder.Append(c); + if (breakSentence) + { + builder.Append("……"); } string description = builder.ToString(); @@ -212,7 +221,7 @@ public class RendererService(ILogger logger, foreach (char c in content.FileContent) { if (char.IsControl(c) || char.IsSymbol(c) - || char.IsSeparator(c)) + || char.IsSeparator(c)) { continue; } @@ -224,4 +233,13 @@ public class RendererService(ILogger logger, count); return count; } + + private static string CalculateReadTime(uint wordCount) + { + // 据说语文教学大纲规定,中国高中问阅读现代文的速度是600字每分钟 + int second = (int)wordCount / 10; + TimeSpan span = new TimeSpan(0, 0, second); + + return span.ToString("mm'分 'ss'秒'"); + } } diff --git a/YaeBlog.Core/Services/TableOfContentService.cs b/YaeBlog.Core/Services/TableOfContentService.cs new file mode 100644 index 0000000..4426016 --- /dev/null +++ b/YaeBlog.Core/Services/TableOfContentService.cs @@ -0,0 +1,20 @@ +using System.Collections.Concurrent; +using YaeBlog.Core.Abstractions; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Services; + +public class TableOfContentService : ITableOfContentService +{ + private readonly ConcurrentDictionary _headlines = []; + + public IReadOnlyDictionary Headlines => _headlines; + + public void AddHeadline(string filename, BlogHeadline headline) + { + if (!_headlines.TryAdd(filename, headline)) + { + throw new InvalidOperationException(); + } + } +} diff --git a/YaeBlog/Components/App.razor b/YaeBlog/Components/App.razor index d9b1f57..3a96ded 100644 --- a/YaeBlog/Components/App.razor +++ b/YaeBlog/Components/App.razor @@ -19,6 +19,10 @@ + + diff --git a/YaeBlog/Components/BlogInformationCard.razor b/YaeBlog/Components/BlogInformationCard.razor index ed3cc6b..fd97c76 100644 --- a/YaeBlog/Components/BlogInformationCard.razor +++ b/YaeBlog/Components/BlogInformationCard.razor @@ -1,6 +1,8 @@ -@using YaeBlog.Core.Services +@using YaeBlog.Core.Abstractions +@using YaeBlog.Core.Models -@inject EssayContentService EssayContentInstance +@inject IEssayContentService Contents +@inject BlogOptions Options - +
+
+ 广而告之 +
+
+ +
+
+

+ @(Options.Announcement) +

+
+
+ diff --git a/YaeBlog/Components/LicenseDisclaimer.razor b/YaeBlog/Components/LicenseDisclaimer.razor new file mode 100644 index 0000000..75aab2e --- /dev/null +++ b/YaeBlog/Components/LicenseDisclaimer.razor @@ -0,0 +1,36 @@ +@using YaeBlog.Core.Models + +@inject BlogOptions Options + + + +@code +{ + [Parameter] public string? EssayAddress { get; set; } +} diff --git a/YaeBlog/Components/LicenseDisclaimer.razor.css b/YaeBlog/Components/LicenseDisclaimer.razor.css new file mode 100644 index 0000000..28c8621 --- /dev/null +++ b/YaeBlog/Components/LicenseDisclaimer.razor.css @@ -0,0 +1,2 @@ +.copyright { +} diff --git a/YaeBlog/Layout/BlogLayout.razor b/YaeBlog/Layout/BlogLayout.razor index a997991..b8c240f 100644 --- a/YaeBlog/Layout/BlogLayout.razor +++ b/YaeBlog/Layout/BlogLayout.razor @@ -3,40 +3,54 @@ @attribute [StreamRendering]
-
-
+
+ - + + diff --git a/YaeBlog/Pages/About.razor b/YaeBlog/Pages/About.razor index 22c02fe..d3847a0 100644 --- a/YaeBlog/Pages/About.razor +++ b/YaeBlog/Pages/About.razor @@ -1,4 +1,11 @@ @page "/blog/about" +@using YaeBlog.Core.Models + +@inject BlogOptions Options + + + 关于 +
@@ -8,77 +15,126 @@
-
- 把字刻在石头上! +
+ 把字刻在石头上!(・’ω’・)
-
-
-

关于我

+
+
+
+

关于我

+
-
-
-
- 计算机科学与技术在读大学生,明光村幼儿园附属大学所属。正处于读书和失业的叠加态。 - 一般在互联网上使用初冬的朝阳或者jackfiled的名字活动。 - 都是ICP备案过的人了,网名似乎没有太大的用处( +
+
+ 计算机科学与技术在读大学生,明光村幼儿园附属大学所属。正处于读书和失业的叠加态。 + 一般在互联网上使用初冬的朝阳或者jackfiled的名字活动。 + 都是ICP备案过的人了,网名似乎没有太大的用处( +
-
-
-
- 主要是一个C#程序员,目前也在尝试写一点Rust。总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。 - 前后端分离的项目本当上手。 - 常常因为现实的压力而写一些C/C++。 - 对于Java和Go的评价很低。 - 日常使用ArchLinux。 +
+
+ 主要是一个C#程序员,目前也在尝试写一点Rust。总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。 + 前后端分离的项目本当上手。 + 常常因为现实的压力而写一些C/C++。 + 对于Java和Go的评价很低。 + 日常使用ArchLinux。 +
-
-
-
- 100%社恐。日常生活是宅在电脑前面自言自语。兴趣活动是读书和看番。 +
+
+ 100%社恐。日常生活是宅在电脑前面自言自语。兴趣活动是读书和看番。 +
-
-
-
- 常常被人批评没有梦想,这里就随便瞎编一下。 - 成为嵌入式工程师,修好桌面上的HoloCubic。 - 完成第一个不是课程设计的个人开源项目。 - 遇到能够搭伙过日子的人也算是一大梦想,虽然社恐人根本不知道从何开始的说, - 什么时候天上才能掉美少女? +
+
+ 常常被人批评没有梦想,这里就随便瞎编一下。 + 成为嵌入式工程师,修好桌面上的HoloCubic。 + 完成第一个不是课程设计的个人开源项目。 + 遇到能够搭伙过日子的人也算是一大梦想,虽然社恐人根本不知道从何开始的说, + 什么时候天上才能掉美少女? +
-
-
-
- 公开的联系渠道是电子邮件。 - 也可以试试在各大平台搜索上面提到的名字。 +
+
+ 公开的联系渠道是电子邮件。 + 也可以试试在各大平台搜索上面提到的名字。 +
-
-
-

关于本站

+
+
+
+

关于本站

+
+
+ +
+
+ 本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用 + Hexo渲染的。 +
+
+ +
+
+ 2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。 +
+
-
-
- 本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用 - Hexo渲染的。 +
+
+
+
+

友链

+
-
-
-
- 2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。 +
+
+ 欢迎所有人联系我添加友链!(´。✪ω✪。`) +
+
+ +
+ @foreach (FriendLink link in Options.Links) + { + + }
diff --git a/YaeBlog/Pages/About.razor.css b/YaeBlog/Pages/About.razor.css index e69de29..80b573c 100644 --- a/YaeBlog/Pages/About.razor.css +++ b/YaeBlog/Pages/About.razor.css @@ -0,0 +1,8 @@ +.link-item { + padding: 1rem; + border-radius: 4px; +} + +.link-item:hover { + background-color: var(--bs-secondary-bg); +} diff --git a/YaeBlog/Pages/Archives.razor b/YaeBlog/Pages/Archives.razor index fee7717..fcf87e9 100644 --- a/YaeBlog/Pages/Archives.razor +++ b/YaeBlog/Pages/Archives.razor @@ -1,8 +1,12 @@ @page "/blog/archives" +@using YaeBlog.Core.Abstractions @using YaeBlog.Core.Models -@using YaeBlog.Core.Services -@inject EssayContentService EssayContentInstance +@inject IEssayContentService Contents + + + 归档 +
@@ -64,9 +68,8 @@ { base.OnInitialized(); - _essays.AddRange(from essay in EssayContentInstance.Essays + _essays.AddRange(from essay in Contents.Essays orderby essay.Value.PublishTime descending group essay by new DateTime(essay.Value.PublishTime.Year, 1, 1)); } - } diff --git a/YaeBlog/Pages/BlogIndex.razor b/YaeBlog/Pages/BlogIndex.razor index 377d43f..9242a4b 100644 --- a/YaeBlog/Pages/BlogIndex.razor +++ b/YaeBlog/Pages/BlogIndex.razor @@ -1,10 +1,14 @@ @page "/blog" +@using YaeBlog.Core.Abstractions @using YaeBlog.Core.Models -@using YaeBlog.Core.Services -@inject EssayContentService EssayContentInstance +@inject IEssayContentService Contents @inject NavigationManager NavigationInstance + + Ricardo's Blog + +
@@ -95,15 +99,15 @@ protected override void OnInitialized() { _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"); return; } - _essays.AddRange(EssayContentInstance.Essays + _essays.AddRange(Contents.Essays .OrderByDescending(p => p.Value.PublishTime) .Skip((_page - 1) * EssaysPerPage) .Take(EssaysPerPage)); diff --git a/YaeBlog/Pages/Essays.razor b/YaeBlog/Pages/Essays.razor index 24fb49e..0a05d69 100644 --- a/YaeBlog/Pages/Essays.razor +++ b/YaeBlog/Pages/Essays.razor @@ -1,9 +1,10 @@ @page "/blog/essays/{BlogKey}" +@using YaeBlog.Core.Abstractions @using YaeBlog.Core.Models -@using YaeBlog.Core.Services +@inject IEssayContentService Contents +@inject ITableOfContentService TableOfContent @inject NavigationManager NavigationInstance -@inject EssayContentService EssayContentInstance @(_essay!.Title) @@ -12,11 +13,11 @@
-

@(_essay!.Title)

+

@(_essay!.Title)

-
+
@(_essay!.PublishTime.ToString("yyyy-MM-dd"))
@@ -29,19 +30,87 @@ }
+
+
+ 总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。 +
+
+
@((MarkupString)_essay!.HtmlContent) + + +
+ +
+
+
+
+
+

+ 文章目录 +

+
+
+ +
+
+ @foreach (BlogHeadline level2 in _headline!.Children) + { +
+ +
+ + @foreach (BlogHeadline level3 in level2.Children) + { + + + @foreach (BlogHeadline level4 in level3.Children) + { + + } + } + } +
+
+ + @if (_headline!.Children.Count == 0) + { +
+
+ 坏了(* Ŏ∀Ŏ),没有在文章中识别到目录 +
+
+ } +
+
+
@code { - [Parameter] - public string? BlogKey { get; set; } + [Parameter] public string? BlogKey { get; set; } private BlogEssay? _essay; + private BlogHeadline? _headline; + protected override void OnInitialized() { base.OnInitialized(); @@ -52,10 +121,15 @@ return; } - if (!EssayContentInstance.TryGet(BlogKey, out _essay)) + if (!Contents.Essays.TryGetValue(BlogKey, out _essay)) { NavigationInstance.NavigateTo("/NotFound"); } + + _headline = TableOfContent.Headlines[BlogKey]; } + private string GenerateSelectorUrl(string selectorId) + => $"/blog/essays/{BlogKey!}#{selectorId}"; + } diff --git a/YaeBlog/Pages/Index.razor b/YaeBlog/Pages/Index.razor index 2ba5cc8..27eeeb4 100644 --- a/YaeBlog/Pages/Index.razor +++ b/YaeBlog/Pages/Index.razor @@ -1,12 +1,16 @@ @page "/" + + Ricardo's Index + +
-
+
Ricardo's Avatar
-
+

初冬的朝阳 (Ricardo Ren)

@@ -27,25 +31,24 @@
+
-
-

恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。

-
- -
-

- 如果您想四处看看,了解一下屏幕对面的人,可以在我的 博客 看看。 - 如果您对于明光村幼儿园某附属技校的计算机教学感兴趣,您可以移步到 - 我的学习笔记, - 虽然这笔记我自己也木有看过。 - 如果您想批判一下我的代码,在 Github 和 - Gitea 都可以找到。 -

-

- 如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。 -

-
+
+

恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。

+
+
+

+ 如果您想四处看看,了解一下屏幕对面的人,可以在我的 博客 看看。 + 如果您对于明光村幼儿园某附属技校的计算机教学感兴趣,您可以移步到 + 我的学习笔记, + 虽然这笔记我自己也木有看过。 + 如果您想批判一下我的代码,在 Github 和 + Gitea 都可以找到。 +

+

+ 如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。 +

diff --git a/YaeBlog/Pages/NotFound.razor b/YaeBlog/Pages/NotFound.razor index cd87b0d..6440b85 100644 --- a/YaeBlog/Pages/NotFound.razor +++ b/YaeBlog/Pages/NotFound.razor @@ -1,5 +1,9 @@ @page "/NotFound" + + 啊~ 页面走丢啦~ + +

NotFound!

diff --git a/YaeBlog/Pages/Tags.razor b/YaeBlog/Pages/Tags.razor index 6319668..c7b75bc 100644 --- a/YaeBlog/Pages/Tags.razor +++ b/YaeBlog/Pages/Tags.razor @@ -1,8 +1,14 @@ @page "/blog/tags/" +@using System.Text.Encodings.Web +@using YaeBlog.Core.Abstractions @using YaeBlog.Core.Models -@using YaeBlog.Core.Services -@inject EssayContentService EssayContentInstance +@inject IEssayContentService Contents +@inject NavigationManager NavigationInstance + + + @(TagName ?? "标签") +
@@ -28,18 +34,19 @@ {