feat: 美化文章界面 (#3)
All checks were successful
Build blog docker image / Build-Blog-Image (push) Successful in 3m57s
All checks were successful
Build blog docker image / Build-Blog-Image (push) Successful in 3m57s
Reviewed-on: #3
This commit is contained in:
parent
ca4f6449d3
commit
a483ddc671
13
YaeBlog.Core/Abstractions/IEssayContentService.cs
Normal file
13
YaeBlog.Core/Abstractions/IEssayContentService.cs
Normal 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);
|
||||||
|
}
|
8
YaeBlog.Core/Abstractions/ITableOfContentService.cs
Normal file
8
YaeBlog.Core/Abstractions/ITableOfContentService.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
using YaeBlog.Core.Models;
|
||||||
|
|
||||||
|
namespace YaeBlog.Core.Abstractions;
|
||||||
|
|
||||||
|
public interface ITableOfContentService
|
||||||
|
{
|
||||||
|
public IReadOnlyDictionary<string, BlogHeadline> Headlines { get; }
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
using Microsoft.AspNetCore.Builder;
|
using AngleSharp;
|
||||||
|
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;
|
||||||
|
@ -17,10 +19,19 @@ public static class WebApplicationBuilderExtensions
|
||||||
|
|
||||||
builder.Services.AddMarkdig();
|
builder.Services.AddMarkdig();
|
||||||
builder.Services.AddYamlParser();
|
builder.Services.AddYamlParser();
|
||||||
|
builder.Services.AddSingleton<IConfiguration>(_ => Configuration.Default);
|
||||||
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.AddSingleton<TableOfContentService>();
|
||||||
|
builder.Services.AddSingleton<ITableOfContentService, TableOfContentService>(provider =>
|
||||||
|
provider.GetRequiredService<TableOfContentService>());
|
||||||
builder.Services.AddTransient<ImagePostRenderProcessor>();
|
builder.Services.AddTransient<ImagePostRenderProcessor>();
|
||||||
|
builder.Services.AddTransient<CodeBlockPostRenderProcessor>();
|
||||||
|
builder.Services.AddTransient<TablePostRenderProcessor>();
|
||||||
|
builder.Services.AddTransient<HeadlinePostRenderProcessor>();
|
||||||
builder.Services.AddTransient<BlogOptions>(provider =>
|
builder.Services.AddTransient<BlogOptions>(provider =>
|
||||||
provider.GetRequiredService<IOptions<BlogOptions>>().Value);
|
provider.GetRequiredService<IOptions<BlogOptions>>().Value);
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,12 @@ namespace YaeBlog.Core.Extensions;
|
||||||
|
|
||||||
public static class WebApplicationExtensions
|
public static class WebApplicationExtensions
|
||||||
{
|
{
|
||||||
public static WebApplication UseMiddleRenderProcessors(this WebApplication application)
|
public static void UseYaeBlog(this WebApplication application)
|
||||||
{
|
{
|
||||||
application.UsePostRenderProcessor<ImagePostRenderProcessor>();
|
application.UsePostRenderProcessor<ImagePostRenderProcessor>();
|
||||||
|
application.UsePostRenderProcessor<CodeBlockPostRenderProcessor>();
|
||||||
return application;
|
application.UsePostRenderProcessor<TablePostRenderProcessor>();
|
||||||
|
application.UsePostRenderProcessor<HeadlinePostRenderProcessor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UsePreRenderProcessor<T>(this WebApplication application) where T : IPreRenderProcessor
|
private static void UsePreRenderProcessor<T>(this WebApplication application) where T : IPreRenderProcessor
|
||||||
|
|
|
@ -12,6 +12,8 @@ public class BlogEssay : IComparable<BlogEssay>
|
||||||
|
|
||||||
public required uint WordCount { get; init; }
|
public required uint WordCount { get; init; }
|
||||||
|
|
||||||
|
public required string ReadTime { get; init; }
|
||||||
|
|
||||||
public List<string> Tags { get; } = [];
|
public List<string> Tags { get; } = [];
|
||||||
|
|
||||||
public required string HtmlContent { get; init; }
|
public required string HtmlContent { get; init; }
|
||||||
|
@ -25,6 +27,7 @@ public class BlogEssay : IComparable<BlogEssay>
|
||||||
PublishTime = PublishTime,
|
PublishTime = PublishTime,
|
||||||
Description = Description,
|
Description = Description,
|
||||||
WordCount = WordCount,
|
WordCount = WordCount,
|
||||||
|
ReadTime = ReadTime,
|
||||||
HtmlContent = newHtmlContent
|
HtmlContent = newHtmlContent
|
||||||
};
|
};
|
||||||
essay.Tags.AddRange(Tags);
|
essay.Tags.AddRange(Tags);
|
||||||
|
|
10
YaeBlog.Core/Models/BlogHeadline.cs
Normal file
10
YaeBlog.Core/Models/BlogHeadline.cs
Normal file
|
@ -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<BlogHeadline> Children { get; } = [];
|
||||||
|
}
|
|
@ -10,10 +10,8 @@ public class BlogOptions
|
||||||
public required string Root { get; set; }
|
public required string Root { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 博客作者
|
/// 博客正文的广而告之
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required string Author { get; set; }
|
|
||||||
|
|
||||||
public required string Announcement { get; set; }
|
public required string Announcement { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -22,21 +20,7 @@ public class BlogOptions
|
||||||
public required int StartYear { get; set; }
|
public required int StartYear { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 博客起始页面的背景图片
|
/// 博客的友链
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required string BannerImage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文章页面的背景图片
|
|
||||||
/// </summary>
|
|
||||||
public required string EssayImage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 博客底部是否显示ICP备案信息
|
|
||||||
/// </summary>
|
|
||||||
public string? RegisterInformation { get; set; }
|
|
||||||
|
|
||||||
public required AboutInfo About { get; set; }
|
|
||||||
|
|
||||||
public required List<FriendLink> Links { get; set; }
|
public required List<FriendLink> Links { get; set; }
|
||||||
}
|
}
|
||||||
|
|
16
YaeBlog.Core/Models/EssayTag.cs
Normal file
16
YaeBlog.Core/Models/EssayTag.cs
Normal 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();
|
||||||
|
}
|
29
YaeBlog.Core/Processors/CodeBlockPostRenderProcessor.cs
Normal file
29
YaeBlog.Core/Processors/CodeBlockPostRenderProcessor.cs
Normal file
|
@ -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<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||||
|
{
|
||||||
|
BrowsingContext context = new(Configuration.Default);
|
||||||
|
IDocument document = await context.OpenAsync(
|
||||||
|
req => req.Content(essay.HtmlContent));
|
||||||
|
|
||||||
|
IEnumerable<IElement> 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);
|
||||||
|
}
|
105
YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs
Normal file
105
YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs
Normal file
|
@ -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<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||||
|
{
|
||||||
|
BrowsingContext browsingContext = new(angleConfiguration);
|
||||||
|
IDocument document = await browsingContext.OpenAsync(req => req.Content(essay.HtmlContent));
|
||||||
|
|
||||||
|
IEnumerable<IElement> elements = from item in document.All
|
||||||
|
where item.LocalName is "h2" or "h3" or "h4"
|
||||||
|
select item;
|
||||||
|
|
||||||
|
BlogHeadline topHeadline = new(essay.Title, "#title");
|
||||||
|
List<BlogHeadline> level2List = [];
|
||||||
|
List<BlogHeadline> level3List = [];
|
||||||
|
List<BlogHeadline> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 找到h4标题的父级标题
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="topHeadline"></param>
|
||||||
|
/// <param name="level2"></param>
|
||||||
|
/// <param name="level3"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static BlogHeadline FindParentHeadline(BlogHeadline topHeadline, List<BlogHeadline> level2,
|
||||||
|
List<BlogHeadline> level3)
|
||||||
|
{
|
||||||
|
BlogHeadline? result = level3.LastOrDefault();
|
||||||
|
if (result is not null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return level2.LastOrDefault() ?? topHeadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 找到h3标题的父级标题
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="topHeadline"></param>
|
||||||
|
/// <param name="level2"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static BlogHeadline FindParentHeadline(BlogHeadline topHeadline, List<BlogHeadline> level2) =>
|
||||||
|
FindParentHeadline(topHeadline, level2, []);
|
||||||
|
|
||||||
|
public string Name => nameof(HeadlinePostRenderProcessor);
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ public class ImagePostRenderProcessor(ILogger<ImagePostRenderProcessor> logger,
|
||||||
return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
|
return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "ImagePostRenderProcessor";
|
public string Name => nameof(ImagePostRenderProcessor);
|
||||||
|
|
||||||
private string GenerateImageLink(string filename, string essayFilename)
|
private string GenerateImageLink(string filename, string essayFilename)
|
||||||
{
|
{
|
||||||
|
|
34
YaeBlog.Core/Processors/TablePostRenderProcessor.cs
Normal file
34
YaeBlog.Core/Processors/TablePostRenderProcessor.cs
Normal file
|
@ -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<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||||
|
{
|
||||||
|
BrowsingContext browsingContext = new(Configuration.Default);
|
||||||
|
IDocument document = await browsingContext.OpenAsync(
|
||||||
|
req => req.Content(essay.HtmlContent));
|
||||||
|
|
||||||
|
IEnumerable<IHtmlTableElement> tableElements = from item in document.All
|
||||||
|
where item.LocalName == "table"
|
||||||
|
select item as IHtmlTableElement;
|
||||||
|
|
||||||
|
foreach (IHtmlTableElement element in tableElements)
|
||||||
|
{
|
||||||
|
IHtmlDivElement divElement = document.CreateElement<IHtmlDivElement>();
|
||||||
|
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);
|
||||||
|
}
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
_tags.Clear();
|
||||||
|
|
||||||
foreach (BlogEssay essay in _essays.Values)
|
foreach (BlogEssay essay in _essays.Values)
|
||||||
{
|
{
|
||||||
foreach (string tag in essay.Tags)
|
foreach (EssayTag essayTag in essay.Tags.Select(tag => new EssayTag(tag)))
|
||||||
{
|
{
|
||||||
if (_tags.TryGetValue(tag, out var list))
|
if (_tags.TryGetValue(essayTag, out List<BlogEssay>? essays))
|
||||||
{
|
{
|
||||||
list.Add(essay);
|
essays.Add(essay);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_tags[tag] = [essay];
|
_tags.Add(essayTag, [essay]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyValuePair<string,List<BlogEssay>> pair in _tags)
|
public bool SearchByUrlEncodedTag(string tag, [NotNullWhen(true)] out List<BlogEssay>? result)
|
||||||
{
|
{
|
||||||
pair.Value.Sort();
|
result = (from item in _tags
|
||||||
}
|
where item.Key.UrlEncodedTagName == tag
|
||||||
}
|
select item.Value).FirstOrDefault();
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<string, int>> Tags => from item in _tags
|
return result is not null;
|
||||||
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))
|
|
||||||
{
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new KeyNotFoundException("Selected tag not found.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Markdig;
|
using Markdig;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using YaeBlog.Core.Abstractions;
|
using YaeBlog.Core.Abstractions;
|
||||||
|
@ -11,7 +12,8 @@ using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace YaeBlog.Core.Services;
|
namespace YaeBlog.Core.Services;
|
||||||
|
|
||||||
public class RendererService(ILogger<RendererService> logger,
|
public partial class RendererService(
|
||||||
|
ILogger<RendererService> logger,
|
||||||
EssayScanService essayScanService,
|
EssayScanService essayScanService,
|
||||||
MarkdownPipeline markdownPipeline,
|
MarkdownPipeline markdownPipeline,
|
||||||
IDeserializer yamlDeserializer,
|
IDeserializer yamlDeserializer,
|
||||||
|
@ -37,12 +39,14 @@ public class RendererService(ILogger<RendererService> logger,
|
||||||
foreach (BlogContent content in preProcessedContents)
|
foreach (BlogContent content in preProcessedContents)
|
||||||
{
|
{
|
||||||
MarkdownMetadata? metadata = TryParseMetadata(content);
|
MarkdownMetadata? metadata = TryParseMetadata(content);
|
||||||
|
uint wordCount = GetWordCount(content);
|
||||||
BlogEssay essay = new()
|
BlogEssay essay = new()
|
||||||
{
|
{
|
||||||
Title = metadata?.Title ?? content.FileName,
|
Title = metadata?.Title ?? content.FileName,
|
||||||
FileName = content.FileName,
|
FileName = content.FileName,
|
||||||
Description = GetDescription(content),
|
Description = GetDescription(content),
|
||||||
WordCount = GetWordCount(content),
|
WordCount = wordCount,
|
||||||
|
ReadTime = CalculateReadTime(wordCount),
|
||||||
PublishTime = metadata?.Date ?? DateTime.Now,
|
PublishTime = metadata?.Date ?? DateTime.Now,
|
||||||
HtmlContent = content.FileContent
|
HtmlContent = content.FileContent
|
||||||
};
|
};
|
||||||
|
@ -51,6 +55,7 @@ public class RendererService(ILogger<RendererService> logger,
|
||||||
{
|
{
|
||||||
essay.Tags.AddRange(metadata.Tags);
|
essay.Tags.AddRange(metadata.Tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
essays.Add(essay);
|
essays.Add(essay);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -125,7 +130,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}'.");
|
||||||
|
@ -172,30 +177,34 @@ public class RendererService(ILogger<RendererService> logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"(?<!\\)[^\#\*_\-\+\`{}\[\]!~]+")]
|
||||||
|
private static partial Regex DescriptionPattern();
|
||||||
|
|
||||||
private string GetDescription(BlogContent content)
|
private string GetDescription(BlogContent content)
|
||||||
{
|
{
|
||||||
const string delimiter = "<!--more-->";
|
const string delimiter = "<!--more-->";
|
||||||
int pos = content.FileContent.IndexOf(delimiter, StringComparison.Ordinal);
|
int pos = content.FileContent.IndexOf(delimiter, StringComparison.Ordinal);
|
||||||
StringBuilder builder = new();
|
bool breakSentence = false;
|
||||||
|
|
||||||
if (pos == -1)
|
if (pos == -1)
|
||||||
{
|
{
|
||||||
// 自动截取前50个字符
|
// 自动截取前50个字符
|
||||||
pos = content.FileContent.Length < 50 ? content.FileContent.Length : 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);
|
||||||
char c = content.FileContent[i];
|
|
||||||
|
|
||||||
if (char.IsControl(c) || char.IsSymbol(c) ||
|
StringBuilder builder = new();
|
||||||
char.IsSeparator(c) || char.IsPunctuation(c) ||
|
foreach (Match match in matches)
|
||||||
char.IsAsciiLetter(c))
|
|
||||||
{
|
{
|
||||||
continue;
|
builder.Append(match.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(c);
|
if (breakSentence)
|
||||||
|
{
|
||||||
|
builder.Append("……");
|
||||||
}
|
}
|
||||||
|
|
||||||
string description = builder.ToString();
|
string description = builder.ToString();
|
||||||
|
@ -224,4 +233,13 @@ public class RendererService(ILogger<RendererService> logger,
|
||||||
count);
|
count);
|
||||||
return 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'秒'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
20
YaeBlog.Core/Services/TableOfContentService.cs
Normal file
20
YaeBlog.Core/Services/TableOfContentService.cs
Normal file
|
@ -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<string, BlogHeadline> _headlines = [];
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, BlogHeadline> Headlines => _headlines;
|
||||||
|
|
||||||
|
public void AddHeadline(string filename, BlogHeadline headline)
|
||||||
|
{
|
||||||
|
if (!_headlines.TryAdd(filename, headline))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,10 @@
|
||||||
|
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="_framework/blazor.web.js"></script>
|
||||||
<script src="bootstrap.bundle.min.js"></script>
|
<script src="bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="clipboard.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const clipboard = new ClipboardJS('.btn');
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
|
@ -22,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>
|
||||||
|
@ -34,9 +36,22 @@
|
||||||
|
|
||||||
<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>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="row justify-content-start fs-5" style="padding-top: 2em">
|
||||||
|
<div class="col-auto">
|
||||||
|
广而告之
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p style="text-indent: 2em">
|
||||||
|
@(Options.Announcement)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
36
YaeBlog/Components/LicenseDisclaimer.razor
Normal file
36
YaeBlog/Components/LicenseDisclaimer.razor
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
@using YaeBlog.Core.Models
|
||||||
|
|
||||||
|
@inject BlogOptions Options
|
||||||
|
|
||||||
|
<div class="row px-2 py-4 copyright border border-primary rounded-1 bg-primary-subtle">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row p-1">
|
||||||
|
<div class="col">
|
||||||
|
文章作者:<a href="https://rrricardo.top" target="_blank">Ricardo Ren</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row p-1">
|
||||||
|
<div class="col">
|
||||||
|
文章地址:
|
||||||
|
<a href="/blog/essays/@(EssayAddress)" target="_blank">
|
||||||
|
@($"https://rrricardo.top/blog/essays/{EssayAddress}")
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row p-1">
|
||||||
|
<div class="col">
|
||||||
|
版权声明:本博客所有文章除特别声明外,均采用
|
||||||
|
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a>
|
||||||
|
许可协议,转载请注明来自
|
||||||
|
<a href="https://rrricardo.top/blog/" target="_blank">Ricardo's Blog</a>。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter] public string? EssayAddress { get; set; }
|
||||||
|
}
|
2
YaeBlog/Components/LicenseDisclaimer.razor.css
Normal file
2
YaeBlog/Components/LicenseDisclaimer.razor.css
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.copyright {
|
||||||
|
}
|
|
@ -3,40 +3,54 @@
|
||||||
@attribute [StreamRendering]
|
@attribute [StreamRendering]
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<div class="row" style="height: 80px">
|
<div class="row d-none d-xl-flex" style="height: 80px">
|
||||||
<div class="px-2 col-8">
|
<div class="px-2 col-9">
|
||||||
<a href="/blog/" class="p-2">
|
<a href="/blog/" class="p-2">
|
||||||
<h4>Ricardo's Blog</h4>
|
<h4>Ricardo's Blog</h4>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-4 d-flex justify-content-around align-items-center">
|
<div class="col-3 d-flex justify-content-around align-items-center">
|
||||||
<a href="/blog/" class="p-2">
|
<a href="/blog/" class="p-2">
|
||||||
<div class="d-inline-flex" style="text-align: center">
|
|
||||||
<Icon Name="@IconName.House" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="@IconSize.x5"/>
|
|
||||||
<h5>首页</h5>
|
<h5>首页</h5>
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/blog/archives/" class="p-2">
|
<a href="/blog/archives/" class="p-2">
|
||||||
<div class="d-inline-flex">
|
|
||||||
<Icon Name="@IconName.Archive" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="IconSize.x5"/>
|
|
||||||
<h5>归档</h5>
|
<h5>归档</h5>
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/blog/tags/" class="p-2">
|
<a href="/blog/tags/" class="p-2">
|
||||||
<div class="d-inline-flex">
|
|
||||||
<Icon Name="@IconName.Tags" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="@IconSize.x5"/>
|
|
||||||
<h5>标签</h5>
|
<h5>标签</h5>
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/blog/about/" class="p-2">
|
<a href="/blog/about/" class="p-2">
|
||||||
<div class="d-inline-flex">
|
|
||||||
<Icon Name="@IconName.Person" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="@IconSize.x5"/>
|
|
||||||
<h5>关于</h5>
|
<h5>关于</h5>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row d-xl-none">
|
||||||
|
<div class="px-2 col-12">
|
||||||
|
<a href="/blog/" class="p-2">
|
||||||
|
<h4>Ricardo's Blog</h4>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-2 col-12 justify-content-end d-flex">
|
||||||
|
<a href="/blog/" class="p-2">
|
||||||
|
<h5>首页</h5>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/blog/archives/" class="p-2">
|
||||||
|
<h5>归档</h5>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/blog/tags/" class="p-2">
|
||||||
|
<h5>标签</h5>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/blog/about/" class="p-2">
|
||||||
|
<h5>关于</h5>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
@page "/blog/about"
|
@page "/blog/about"
|
||||||
|
@using YaeBlog.Core.Models
|
||||||
|
|
||||||
|
@inject BlogOptions Options
|
||||||
|
|
||||||
|
<PageTitle>
|
||||||
|
关于
|
||||||
|
</PageTitle>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -8,12 +15,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col fst-italic py-4">
|
<div class="col fst-italic py-2">
|
||||||
把字刻在石头上!
|
把字刻在石头上!(・’ω’・)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row p-2">
|
<div class="row p-2">
|
||||||
|
<div class="col">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>关于我</h3>
|
<h3>关于我</h3>
|
||||||
|
@ -61,8 +69,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row p-2">
|
<div class="row p-2">
|
||||||
|
<div class="col">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>关于本站</h3>
|
<h3>关于本站</h3>
|
||||||
|
@ -82,6 +92,52 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row p-2">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>友链</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row py-2">
|
||||||
|
<div class="col fst-italic">
|
||||||
|
欢迎所有人联系我添加友链!(´。✪ω✪。`)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row py-2">
|
||||||
|
@foreach (FriendLink link in Options.Links)
|
||||||
|
{
|
||||||
|
<div class="col-sm-12 col-md-4 col-lg-3">
|
||||||
|
<a href="@(link.Link)" target="_blank" class="m-3">
|
||||||
|
<div class="row link-item">
|
||||||
|
<div class="col-4">
|
||||||
|
<Image Src="@(link.AvatarImage)" Alt="@(link.Name)" Style="border-radius: 50%"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-8">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto fs-5">
|
||||||
|
@(link.Name)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto fst-italic">
|
||||||
|
@(link.Description)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.link-item {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item:hover {
|
||||||
|
background-color: var(--bs-secondary-bg);
|
||||||
|
}
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
@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>
|
||||||
|
Ricardo's Blog
|
||||||
|
</PageTitle>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-9">
|
<div class="col-sm-12 col-md-9">
|
||||||
|
@ -95,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));
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
@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 ITableOfContentService TableOfContent
|
||||||
@inject NavigationManager NavigationInstance
|
@inject NavigationManager NavigationInstance
|
||||||
@inject EssayContentService EssayContentInstance
|
|
||||||
|
|
||||||
<PageTitle>
|
<PageTitle>
|
||||||
@(_essay!.Title)
|
@(_essay!.Title)
|
||||||
|
@ -12,11 +13,11 @@
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<h1>@(_essay!.Title)</h1>
|
<h1 id="title">@(_essay!.Title)</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row p-4">
|
<div class="row px-4 py-1">
|
||||||
<div class="col-auto fw-light">
|
<div class="col-auto fw-light">
|
||||||
@(_essay!.PublishTime.ToString("yyyy-MM-dd"))
|
@(_essay!.PublishTime.ToString("yyyy-MM-dd"))
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,19 +30,87 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row px-4 py-1">
|
||||||
|
<div class="col-auto fw-light">
|
||||||
|
总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8 col-md-12">
|
<div class="col-lg-8 col-md-12">
|
||||||
@((MarkupString)_essay!.HtmlContent)
|
@((MarkupString)_essay!.HtmlContent)
|
||||||
|
|
||||||
|
<LicenseDisclaimer EssayAddress="@BlogKey"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4 col-md-12">
|
||||||
|
<div class="row sticky-lg-top justify-content-center">
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto">
|
||||||
|
<h3 style="margin-block-start: 1em; margin-block-end: 0.5em">
|
||||||
|
文章目录
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="padding-left: 10px">
|
||||||
|
<div class="col-auto">
|
||||||
|
@foreach (BlogHeadline level2 in _headline!.Children)
|
||||||
|
{
|
||||||
|
<div class="row py-1">
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="@(GenerateSelectorUrl(level2.SelectorId))">@(level2.Title)</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach (BlogHeadline level3 in level2.Children)
|
||||||
|
{
|
||||||
|
<div class="row py-1">
|
||||||
|
<div class="col-auto">
|
||||||
|
<a style="padding-left: 20px" href="@GenerateSelectorUrl(level3.SelectorId)">
|
||||||
|
@(level3.Title)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach (BlogHeadline level4 in level3.Children)
|
||||||
|
{
|
||||||
|
<div class="row py-1">
|
||||||
|
<div class="col-auto">
|
||||||
|
<a style="padding-left: 40px" href="@(GenerateSelectorUrl(level4.SelectorId))">
|
||||||
|
@(level4.Title)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (_headline!.Children.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<div class="col fst-italic">
|
||||||
|
坏了(* Ŏ∀Ŏ),没有在文章中识别到目录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter] public string? BlogKey { get; set; }
|
||||||
public string? BlogKey { get; set; }
|
|
||||||
|
|
||||||
private BlogEssay? _essay;
|
private BlogEssay? _essay;
|
||||||
|
|
||||||
|
private BlogHeadline? _headline;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
base.OnInitialized();
|
base.OnInitialized();
|
||||||
|
@ -52,10 +121,15 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EssayContentInstance.TryGet(BlogKey, out _essay))
|
if (!Contents.Essays.TryGetValue(BlogKey, out _essay))
|
||||||
{
|
{
|
||||||
NavigationInstance.NavigateTo("/NotFound");
|
NavigationInstance.NavigateTo("/NotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_headline = TableOfContent.Headlines[BlogKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GenerateSelectorUrl(string selectorId)
|
||||||
|
=> $"/blog/essays/{BlogKey!}#{selectorId}";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
@page "/"
|
@page "/"
|
||||||
|
|
||||||
|
<PageTitle>
|
||||||
|
Ricardo's Index
|
||||||
|
</PageTitle>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row py-4">
|
<div class="row py-4">
|
||||||
<div class="col-4">
|
<div class="col-lg-4 col-12 p-5 p-lg-0">
|
||||||
<Image Src="images/avatar.png" Alt="Ricardo's Avatar"/>
|
<Image Src="images/avatar.png" Alt="Ricardo's Avatar"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-8">
|
<div class="col-lg-8 col-12">
|
||||||
<div class="container px-3">
|
<div class="container px-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h4 class="fw-bold">初冬的朝阳 (Ricardo Ren)</h4>
|
<h4 class="fw-bold">初冬的朝阳 (Ricardo Ren)</h4>
|
||||||
|
@ -27,6 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row" style="padding-top: 80px">
|
<div class="row" style="padding-top: 80px">
|
||||||
<p class="fs-5">恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。</p>
|
<p class="fs-5">恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。</p>
|
||||||
|
@ -45,8 +50,6 @@
|
||||||
如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。
|
如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
@page "/NotFound"
|
@page "/NotFound"
|
||||||
|
|
||||||
|
<PageTitle>
|
||||||
|
啊~ 页面走丢啦~
|
||||||
|
</PageTitle>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3>NotFound!</h3>
|
<h3>NotFound!</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ WebApplication application = builder.Build();
|
||||||
|
|
||||||
application.UseStaticFiles();
|
application.UseStaticFiles();
|
||||||
application.UseAntiforgery();
|
application.UseAntiforgery();
|
||||||
application.UseMiddleRenderProcessors();
|
application.UseYaeBlog();
|
||||||
|
|
||||||
application.MapRazorComponents<App>()
|
application.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
|
@ -8,18 +8,8 @@
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Blog": {
|
"Blog": {
|
||||||
"Root": "source",
|
"Root": "source",
|
||||||
"Author": "Ricardo Ren",
|
|
||||||
"Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。",
|
"Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。",
|
||||||
"StartYear": 2021,
|
"StartYear": 2021,
|
||||||
"ProjectName": "Blog",
|
|
||||||
"BannerImage": "images/banner.png",
|
|
||||||
"EssayImage": "images/banner.png",
|
|
||||||
"RegisterInformation": "蜀ICP备2022004429号-1",
|
|
||||||
"About": {
|
|
||||||
"Introduction": "A CS Student",
|
|
||||||
"Description": "还太菜了,没有做出太多的贡献。",
|
|
||||||
"AvatarImage": "images/avatar.png"
|
|
||||||
},
|
|
||||||
"Links": [
|
"Links": [
|
||||||
{
|
{
|
||||||
"Name": "Ichirinko",
|
"Name": "Ichirinko",
|
||||||
|
|
|
@ -5,8 +5,6 @@ tags:
|
||||||
- 随笔
|
- 随笔
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 2021年终总结
|
|
||||||
2021年已经过去,2022年已经来临。每每一年开始的时候,我都会展开一张纸或者新建一个文档,思量着又是一年时光,也该同诸大杂志一般,写几句意味深长的话语,怀念过去的时光,也祝福未来的自己。可往往脑海中已是三万字的长篇,落在笔头却又是一个字都没有了。
|
2021年已经过去,2022年已经来临。每每一年开始的时候,我都会展开一张纸或者新建一个文档,思量着又是一年时光,也该同诸大杂志一般,写几句意味深长的话语,怀念过去的时光,也祝福未来的自己。可往往脑海中已是三万字的长篇,落在笔头却又是一个字都没有了。
|
||||||
如今跨年的时候已经过去,朋友圈中已经不见文案的踪影,我也该重新提笔,细说自己2021年中做过的种种。
|
如今跨年的时候已经过去,朋友圈中已经不见文案的踪影,我也该重新提笔,细说自己2021年中做过的种种。
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@ date: 2022-12-30 14:58:12
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 2022年终总结
|
|
||||||
|
|
||||||
2022是困难的一年。我们需要为2023年做好准备。
|
2022是困难的一年。我们需要为2023年做好准备。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -6,9 +6,6 @@ typora-root-url: 2022-summer-vacation
|
||||||
date: 2022-08-22 15:39:13
|
date: 2022-08-22 15:39:13
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 2022年暑假总结
|
|
||||||
|
|
||||||
在8个月的漫长寒假的最后两个月,~~也就是俗称的暑假中~~,我都干了些什么?
|
在8个月的漫长寒假的最后两个月,~~也就是俗称的暑假中~~,我都干了些什么?
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -7,8 +7,6 @@ typora-root-url: big-homework
|
||||||
date: 2022-07-27 11:34:49
|
date: 2022-07-27 11:34:49
|
||||||
---
|
---
|
||||||
|
|
||||||
# 代码大作业初体验
|
|
||||||
|
|
||||||
在大学也呆了一年了,终于遇上了第一个需要多人合作的写代码项目。从四月底分组完成,任务部署下来到七月初接近尾声,在这两个多月的时间里,也算是经历了不少,学到了不少。
|
在大学也呆了一年了,终于遇上了第一个需要多人合作的写代码项目。从四月底分组完成,任务部署下来到七月初接近尾声,在这两个多月的时间里,也算是经历了不少,学到了不少。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -7,10 +7,6 @@ tags:
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 建立博客过程的记录
|
|
||||||
|
|
||||||
## 博客之始
|
|
||||||
|
|
||||||
当我已经在Python的浩瀚大海遨(zheng)游(zha)了半个暑假后,我决定尝试一下传说中程序员专用的学(zhuang)习(bi)手(fangfa)段(fa)——建立自己的个人博客。作为一个半懂不懂的Python程序员,心中冒出的第一个想法自然是采用Python的Django作为开发自己的个人博客的手段。然而,在阅读了[用Django搭建个人博客](https://www.dusaiphoto.com/article/2/)等的其他人搭建这类动态博客的过程记录之后,我便义无反顾的转向了采用javascript开发的博客框架[Hexo](https://hexo.io),<del>说好的Python信仰呢</del>。无他,唯简单尔。
|
当我已经在Python的浩瀚大海遨(zheng)游(zha)了半个暑假后,我决定尝试一下传说中程序员专用的学(zhuang)习(bi)手(fangfa)段(fa)——建立自己的个人博客。作为一个半懂不懂的Python程序员,心中冒出的第一个想法自然是采用Python的Django作为开发自己的个人博客的手段。然而,在阅读了[用Django搭建个人博客](https://www.dusaiphoto.com/article/2/)等的其他人搭建这类动态博客的过程记录之后,我便义无反顾的转向了采用javascript开发的博客框架[Hexo](https://hexo.io),<del>说好的Python信仰呢</del>。无他,唯简单尔。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -7,7 +7,6 @@ typora-root-url: c-include-problems
|
||||||
date: 2022-05-08 11:35:19
|
date: 2022-05-08 11:35:19
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
最近在完成一门`C`语言课程的大作业,课设老师要求我们将程序分模块的开发。在编写项目头文件的时候,遇到了一些令本菜鸡大开眼界的问题。
|
最近在完成一门`C`语言课程的大作业,课设老师要求我们将程序分模块的开发。在编写项目头文件的时候,遇到了一些令本菜鸡大开眼界的问题。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -6,8 +6,7 @@ tags:
|
||||||
date: 2022-11-11 22:20:25
|
date: 2022-11-11 22:20:25
|
||||||
---
|
---
|
||||||
|
|
||||||
# 编译MediaPipe框架
|
编译MediaPipe框架。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
最近开始研究自己的大创项目,一个关于动作捕捉的小玩意儿,第一步就是~~抄袭开源代码~~借鉴他人优秀成果。在众多的项目中,我看上了这个Google开源的优秀框架,先把这个项目在本地上跑起来再说。这篇文章就记录了我编译这个框架的过程。
|
最近开始研究自己的大创项目,一个关于动作捕捉的小玩意儿,第一步就是~~抄袭开源代码~~借鉴他人优秀成果。在众多的项目中,我看上了这个Google开源的优秀框架,先把这个项目在本地上跑起来再说。这篇文章就记录了我编译这个框架的过程。
|
||||||
|
|
|
@ -7,9 +7,6 @@ date: 2023-01-15 22:23:08
|
||||||
typora-root-url: daily-linux-0
|
typora-root-url: daily-linux-0
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 日用Linux挑战 第0篇
|
|
||||||
|
|
||||||
在将开发重心移到`WSL`上一年之后,我最终还是决定完全抛弃Windows,转向使用Linux作为我日常使用的主力系统。目前,我已经使用Linux作为主力系统一个月了。
|
在将开发重心移到`WSL`上一年之后,我最终还是决定完全抛弃Windows,转向使用Linux作为我日常使用的主力系统。目前,我已经使用Linux作为主力系统一个月了。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -7,8 +7,6 @@ date: 2023-03-08 22:37:29
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 日用Linux挑战第1篇
|
|
||||||
|
|
||||||
从去年12月底正式切换到`Linux`开始算起,我日常使用`Linux`已经过去了2个月的时间。在本系列的上一篇文章——[日用Linux挑战 第0篇 - Ricardo的博客](https://rrricardo.top/blog/2023/01/15/daily-linux-0/)中,我讲述了我配置自己的`Arch Linux`的过程,还小小的赞扬了一波`Linux`在近些年来取得的进展。但是在这篇文章中,我将重点指出日常使用过程中遇到的问题和困难。
|
从去年12月底正式切换到`Linux`开始算起,我日常使用`Linux`已经过去了2个月的时间。在本系列的上一篇文章——[日用Linux挑战 第0篇 - Ricardo的博客](https://rrricardo.top/blog/2023/01/15/daily-linux-0/)中,我讲述了我配置自己的`Arch Linux`的过程,还小小的赞扬了一波`Linux`在近些年来取得的进展。但是在这篇文章中,我将重点指出日常使用过程中遇到的问题和困难。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -8,8 +8,6 @@ typora-root-url: daily-linux-2
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 日用Linux挑战 第2篇
|
|
||||||
|
|
||||||
使用`Linux`6个月,我成功戒掉了原神。
|
使用`Linux`6个月,我成功戒掉了原神。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -8,8 +8,6 @@ date: 2023-09-04 14:47:46
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 日用Linux挑战 第三篇
|
|
||||||
|
|
||||||
成也开源,败也开源。
|
成也开源,败也开源。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
---
|
|
||||||
title: .net从入门到放弃再到入门
|
|
||||||
tags:
|
|
||||||
- 技术笔记
|
|
||||||
- dotnet
|
|
||||||
date: 2022-08-21 16:59:08
|
|
||||||
---
|
|
||||||
|
|
||||||
# .net从入门到放弃再到入门
|
|
||||||
|
|
||||||
我宣布下面的内容都是我在胡扯。在写了一学期的`Java`和`Springboot`之后,我的评价是`Java`是什么垃圾东西,`C#`才是永远的神!
|
|
||||||
|
|
||||||
~~这是一篇专业的.net劝退指南,详细记述了我为啥选择了.net又为啥选择了放弃。~~
|
|
||||||
|
|
||||||
<!--more-->
|
|
||||||
|
|
||||||
### 开端——WPF桌面应用
|
|
||||||
|
|
||||||
~~在今年年初的时候,受到[通知大全](https://squidward.top/)启发,我决定编写一款自己的DDL管理应用程序。在一开始,我打算先在Windows平台上编写,当时我了解到Windows上最正统的桌面应用程序开发方式就是采用微软自家的.net平台开发,同时在[知乎](https://zhihu.com)上一群人在吹`C#`在设计上是如何如何的优于`JAVA`,当然在这里没有说他们的观点错误的意思,把我忽悠的一愣一愣的。.net在当时已经有了比较老旧的`WinForms`框架、比较流行的`WPF`框架和最新的`MAUI`框架。~~秉持着中国人中庸的思想潮流~~,我选择了`WPF`图形框架作为我当时开发的框架,从此入了.net平台的坑。~~
|
|
||||||
|
|
||||||
> 如今这个桌面端的项目已经基本烂尾,项目开源在[github](https://github.com/jackfiled/PostCalendarWindows),算是警醒后来的我在技术选型时应该更加的慎重
|
|
||||||
|
|
||||||
~~说实话,在开发的一开始,我就感觉力不从心。~~
|
|
||||||
|
|
||||||
~~第一,官方文档好但不完全好。微软虽然提供了本地化过的文档,但是有很大比例都是机翻的文档。我觉得吧,放机翻的文档还不如直接放英文原文的文档。同时,文档的大部分都是API列表一类的参考资料,对于初学者来说比较重要的“实用教程”等等部分内容较少,虽然文档全面而详细,但对于初学者来说并没有很大的帮助。毕竟我们不知道在数以万计的API中,哪个才能实现自己的需求。~~
|
|
||||||
|
|
||||||
~~第二,国内缺少.net相关的社区氛围。这点也是最为劝退的地方,相比于`JAVA`在国内广泛的应用和丰富的社区内容,国内.net和`C#`相关的内容除了在`Unity`游戏开发中还算广泛,其他称得上是乏善可陈。~~
|
|
||||||
|
|
||||||
~~在一堆好几年前的博客和半懂不懂的英文文档中沉浮一个月之后,我总算是写出了一个可以运行的成品。随着新学期的到来,以学业繁忙为借口,我停止了这个应用的开发。~~
|
|
||||||
|
|
||||||
### 重启——服务器开发
|
|
||||||
|
|
||||||
~~在经历了桌面应用的失败之后,我转移了自己的开发重心,开始了移动应用的开发,这次我没有坚持被微软的`MAUI`那八字还没有一撇的技术忽悠进去,选择了`flutter`框架搞开发。有应用自然就得有提供数据的后端,在开发服务器时,我又被微软的`ASP.NET`给“忽悠”了进去。~~
|
|
||||||
|
|
||||||
~~平心而论,`ASP.NET`开发的过程比上文中的桌面应用程序的开发还是要顺利不少。毕竟现在采用`B/S`架构的服务更多,个人感觉国内应用这项技术的人也不少,相关的技术资料也就不少,我入门的过程也就流畅许多。~~
|
|
||||||
|
|
||||||
~~虽然但是,在开发的过程中劝退的地方也不少。第一是微软自己的版本更迭,尤其是在微软宣布`dotnet core`,将.net开源之后,个人感觉国内的开发者似乎不是很感冒,还是用着原来的老一套。第二是和现在的技术流行方向不同,原本采用`JAVA`搞服务器的开发不太可能再更换语言,第二新入行的开发者也会选择当下流行的`GO`等语言,这就导致在开发中有一种单打独斗的感觉,很难找到人同你合作。~~
|
|
||||||
|
|
||||||
### 放弃
|
|
||||||
|
|
||||||
~~在坚持学习.net八个月之后,我还是决定放弃,转向学习`JAVA`。~~
|
|
||||||
|
|
||||||
- ~~没人用的技术的技术不要硬刚,即使是好技术也不要。没人用的技术就像一潭死水,只有流动起来,才能孕育生机和活力,否则只能在原地腐烂。~~
|
|
||||||
- ~~之于我而言,语言不仅仅是开发软件的工具,更是需要计算机相关知识的工具。转向`JAVA`的原因之一就是我目前学习的数据结合课——伯克利的`CS61b`就是采用`JAVA`作为编程语言的。~~
|
|
||||||
|
|
||||||
~~虽然我已经决定将我的主力语言转向`JAVA`,~~但不得不承认`C#`在语言上的特性还是很不错的,以后我的重心不会放在`C#`上,但是他也不会从的技能树上消失。也许在若干年之后,我的主力语言又变回`C#`了(笑)。
|
|
||||||
|
|
||||||
> 全文的最后一句话可能是我现在唯一认同的了
|
|
|
@ -6,8 +6,6 @@ tags:
|
||||||
typora-root-url: 环境配置
|
typora-root-url: 环境配置
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 环境配置备忘录
|
|
||||||
电脑上的环境三天两头出问题,写下一个备忘录记录一下电脑上环境的配置过程。
|
电脑上的环境三天两头出问题,写下一个备忘录记录一下电脑上环境的配置过程。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -5,7 +5,7 @@ tags:
|
||||||
date: 2022-12-31 13:38:19
|
date: 2022-12-31 13:38:19
|
||||||
---
|
---
|
||||||
|
|
||||||
# 原神抽卡研究一
|
实际上是“概率论和随机过程”课程的期末小论文。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ tags:
|
||||||
typora-root-url: 安装pytorch,来有深度的学习
|
typora-root-url: 安装pytorch,来有深度的学习
|
||||||
---
|
---
|
||||||
|
|
||||||
# 深度学习预备篇——安装深度学习框架——pytorch
|
鄙人在下不才我精通深度学习框架的安装和卸载。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,6 @@ date: 2022-06-13 16:17:27
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 大学生用啥配置——计算机专业
|
|
||||||
|
|
||||||
> 本文是应B站UP主[远古时代装机猿](https://space.bilibili.com/35359510)发起的[大学生用啥配置](https://www.bilibili.com/video/BV1kZ4y1i7Le)公益活动而写
|
> 本文是应B站UP主[远古时代装机猿](https://space.bilibili.com/35359510)发起的[大学生用啥配置](https://www.bilibili.com/video/BV1kZ4y1i7Le)公益活动而写
|
||||||
>
|
>
|
||||||
> 目前某不知名211大学计算机专业在读
|
> 目前某不知名211大学计算机专业在读
|
||||||
|
|
|
@ -6,9 +6,6 @@ tags:
|
||||||
date: 2023-10-09 23:56:34
|
date: 2023-10-09 23:56:34
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 解决云原神无法在Linux中浏览器下运行的问题
|
|
||||||
|
|
||||||
本文为转载`bilibili`用户[@SocialismTeen](https://space.bilibili.com/33027704)在他的[专栏](https://www.bilibili.com/read/cv26576757)中给出的解决办法。
|
本文为转载`bilibili`用户[@SocialismTeen](https://space.bilibili.com/33027704)在他的[专栏](https://www.bilibili.com/read/cv26576757)中给出的解决办法。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -6,10 +6,10 @@ tags:
|
||||||
date: 2024-1-12 20:10:06
|
date: 2024-1-12 20:10:06
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--more-->
|
|
||||||
|
|
||||||
让Minecraft游戏使用`Wayland`显示协议。
|
让Minecraft游戏使用`Wayland`显示协议。
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
## Update At 2024-2-24
|
## Update At 2024-2-24
|
||||||
|
|
||||||
在两天前,2024年的2月22日,`glfw`释出了一个新版本`3.4`。在新版本中,`glfw`大幅强化了对于`wayland`显示协议的支持,在默认情况下就会直接使用`wayland`显示协议。但是为了能够正常的运行`minecraft`,还需要对源代码进行修改:
|
在两天前,2024年的2月22日,`glfw`释出了一个新版本`3.4`。在新版本中,`glfw`大幅强化了对于`wayland`显示协议的支持,在默认情况下就会直接使用`wayland`显示协议。但是为了能够正常的运行`minecraft`,还需要对源代码进行修改:
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
---
|
|
||||||
title: PostCalendar介绍
|
|
||||||
typora-root-url: PostCalendar介绍
|
|
||||||
date: 2022-03-05 14:18:50
|
|
||||||
tags:
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
# PostCalendar介绍
|
|
||||||
|
|
||||||
> 本文是[PostCalendarWindows](https://https://github.com/jackfiled/PostCalendarWindows)README文件的国内镜像
|
|
||||||
|
|
||||||
一款集日程管理与DDL管理于一身的日历软件。
|
|
||||||
|
|
||||||
下载地址:[GithubRelease](https://github.com/jackfiled/PostCalendarWindows/releases)
|
|
||||||
|
|
||||||
# 开发未完成,仍在内测阶段
|
|
||||||
|
|
||||||
<!--more-->
|
|
||||||
## 支持的功能
|
|
||||||
|
|
||||||
### 日历部分
|
|
||||||
|
|
||||||
![日历部分截图](Calendar.png)
|
|
||||||
|
|
||||||
1. 支持日历事件的添加,删除,修改。
|
|
||||||
2. 支持读取教务处自动生成的excel课表文件。
|
|
||||||
|
|
||||||
> 这个功能需要电脑上安装excel应用程序.
|
|
||||||
>
|
|
||||||
> 目前这个功能仅作实验性的支持,不保证excel读取的完全准确。
|
|
||||||
>
|
|
||||||
> > 使用帮助:在教务处网站“学期理论课表”页面有打印按钮,可以下载一个excel表格。
|
|
||||||
> >
|
|
||||||
> > 下载完成后,在软件里点击“导入excel课表”按钮,选择excel文件下载的位置,即可自动导入。
|
|
||||||
|
|
||||||
### DDL部分
|
|
||||||
|
|
||||||
![DDL部分截图](DDL.png)
|
|
||||||
|
|
||||||
1. 支持DDL事件的添加,修改,完成,删除。
|
|
||||||
|
|
||||||
2. 可以将活动界面中DDL类型的事件直接添加到个人DDL中。
|
|
||||||
|
|
||||||
### 活动部分
|
|
||||||
|
|
||||||
![活动部分截图](Activity.png)
|
|
||||||
|
|
||||||
**本部分未完成,设想中将与[DDL网站](http://squidward.top)的数据进行同步**
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
软件依赖于.net6.0,请点击[链接](https://dotnet.microsoft.com/zh-cn/download/dotnet/thank-you/runtime-desktop-6.0.2-windows-x64-installer)下载.net6.0运行时。
|
|
||||||
在安装完成或者确认电脑上已安装.net6.0运行时之后,点击下载旁边的release包,双击下载文件中的PostCalendarWindows.exe即可使用。
|
|
BIN
YaeBlog/source/post-calendar/Activity.png
(Stored with Git LFS)
BIN
YaeBlog/source/post-calendar/Activity.png
(Stored with Git LFS)
Binary file not shown.
BIN
YaeBlog/source/post-calendar/Calendar.png
(Stored with Git LFS)
BIN
YaeBlog/source/post-calendar/Calendar.png
(Stored with Git LFS)
Binary file not shown.
BIN
YaeBlog/source/post-calendar/DDL.png
(Stored with Git LFS)
BIN
YaeBlog/source/post-calendar/DDL.png
(Stored with Git LFS)
Binary file not shown.
|
@ -5,8 +5,6 @@ tags:
|
||||||
- 学习资料
|
- 学习资料
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 程序设计与计算导论笔记
|
|
||||||
直接扒的老黄的PPT ,简直毫无参考价值。
|
直接扒的老黄的PPT ,简直毫无参考价值。
|
||||||
不保证笔记的绝对正确性。
|
不保证笔记的绝对正确性。
|
||||||
如果导致考试爆炸,不承担任何责任。
|
如果导致考试爆炸,不承担任何责任。
|
||||||
|
|
|
@ -7,8 +7,6 @@ typora-root-url: qt-learning
|
||||||
date: 2022-07-01 14:32:39
|
date: 2022-07-01 14:32:39
|
||||||
---
|
---
|
||||||
|
|
||||||
# 初学Qt的一点小笔记
|
|
||||||
|
|
||||||
最近的大作业需要用 `C/C++`的技术栈实现一个图形化界面,`Qt`作为C++图形化框架久负盛名,正好借着这个写大作业的机会学习一下这个应用广泛的框架。
|
最近的大作业需要用 `C/C++`的技术栈实现一个图形化界面,`Qt`作为C++图形化框架久负盛名,正好借着这个写大作业的机会学习一下这个应用广泛的框架。
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ tags:
|
||||||
- 技术笔记
|
- 技术笔记
|
||||||
---
|
---
|
||||||
|
|
||||||
# 安装Visual Studio 2019中遇到的坑
|
|
||||||
在某个月黑风高的夜晚,我在折腾了很久的Python之后,突然感觉自己应该去学学C和C++,于是乎我便打算折腾一下在vscode上写C和C++。在网上一番搜寻之后,我发现了这篇[知乎文章](https://zhuanlan.zhihu.com/p/87864677)和这篇[知乎文章](https://zhuanlan.zhihu.com/p/147366852),然后我就被安装MinGW编译器和配置一大堆的json文件给干碎了。<br>
|
在某个月黑风高的夜晚,我在折腾了很久的Python之后,突然感觉自己应该去学学C和C++,于是乎我便打算折腾一下在vscode上写C和C++。在网上一番搜寻之后,我发现了这篇[知乎文章](https://zhuanlan.zhihu.com/p/87864677)和这篇[知乎文章](https://zhuanlan.zhihu.com/p/147366852),然后我就被安装MinGW编译器和配置一大堆的json文件给干碎了。<br>
|
||||||
于是,我决定转向传说中的宇宙第一IDE——Visual Studio。<br>
|
于是,我决定转向传说中的宇宙第一IDE——Visual Studio。<br>
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -6,8 +6,7 @@ tags:
|
||||||
date: 2023-07-29 15:20:02
|
date: 2023-07-29 15:20:02
|
||||||
---
|
---
|
||||||
|
|
||||||
|
如题。
|
||||||
# SpringBoot自定义注解实现权限控制
|
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ tags:
|
||||||
- 技术笔记
|
- 技术笔记
|
||||||
---
|
---
|
||||||
|
|
||||||
# 优雅地使用学校VPN
|
睿智的Global Protect!
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ tags:
|
||||||
- 技术笔记
|
- 技术笔记
|
||||||
---
|
---
|
||||||
|
|
||||||
# 无论何处,都可以使用自己定制的VSCode开发环境
|
|
||||||
众所周知,VSCode作为大微软家开发的开源编辑器,一经发布便受到了两广大程序员群体的欢迎。如果我们深入的了解一下VSCode,就会知道VSCode是基于Electron框架构建的Web应用程序,而Electron框架是基于Web技术来开发桌面应用程序,即VSCode只要稍加改造,就可以流畅的在浏览器中运行。那么我们如何才能在浏览器打开一个VSCode呢?
|
众所周知,VSCode作为大微软家开发的开源编辑器,一经发布便受到了两广大程序员群体的欢迎。如果我们深入的了解一下VSCode,就会知道VSCode是基于Electron框架构建的Web应用程序,而Electron框架是基于Web技术来开发桌面应用程序,即VSCode只要稍加改造,就可以流畅的在浏览器中运行。那么我们如何才能在浏览器打开一个VSCode呢?
|
||||||
最简单的方法是在浏览器中输入[这个网址](https://vscode.dev),就可以在浏览器中打开一个VSCode Online,这个版本的VSCode可以支持打开本地的文件,并且进行编辑。不过这个编辑器并不支持大部分的插件,而且并不支持程序的编译与运行,并不是一个可以开箱即用的编辑器。那么还有什么办法可以让我们拥有一个联网即可得的个人定制化开发环境呢?
|
最简单的方法是在浏览器中输入[这个网址](https://vscode.dev),就可以在浏览器中打开一个VSCode Online,这个版本的VSCode可以支持打开本地的文件,并且进行编辑。不过这个编辑器并不支持大部分的插件,而且并不支持程序的编译与运行,并不是一个可以开箱即用的编辑器。那么还有什么办法可以让我们拥有一个联网即可得的个人定制化开发环境呢?
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
|
@ -7,9 +7,6 @@ typora-root-url: wsl-setup-csapp
|
||||||
date: 2022-09-03 19:02:58
|
date: 2022-09-03 19:02:58
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 利用WSL设置CSAPP实验环境
|
|
||||||
|
|
||||||
`CSAPP`这本书为自学的学生们提供了不少的`LAB`供大家在联系中提高,但是这些`LAB`的编写普遍需要一个`Linux`的实验环境,但是目前大多数人手中的环境都是`Windows`平台,没有办法原生的运行这些`LAB`。在以前的实践中,这个问题往往是通过安装虚拟机来解决的,但是现在我们有了更好的解决方案——`Windows Subsystem for Linux`,简称`WSL`。
|
`CSAPP`这本书为自学的学生们提供了不少的`LAB`供大家在联系中提高,但是这些`LAB`的编写普遍需要一个`Linux`的实验环境,但是目前大多数人手中的环境都是`Windows`平台,没有办法原生的运行这些`LAB`。在以前的实践中,这个问题往往是通过安装虚拟机来解决的,但是现在我们有了更好的解决方案——`Windows Subsystem for Linux`,简称`WSL`。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
7
YaeBlog/wwwroot/clipboard.min.js
vendored
Normal file
7
YaeBlog/wwwroot/clipboard.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,3 +1,33 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: "Font Awesome 6 Free";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: block;
|
||||||
|
src: url(fonts/fa-regular-400.woff2) format("woff2"), url(fonts/fa-regular-400.ttf) format("truetype")
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Font Awesome 6 Free";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: block;
|
||||||
|
src: url(fonts/fa-solid-900.woff2) format("woff2"), url(fonts/fa-solid-900.ttf) format("truetype")
|
||||||
|
}
|
||||||
|
|
||||||
|
.essay-image {
|
||||||
|
width: 90%;
|
||||||
|
display: block;
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18),
|
||||||
|
0 4px 15px 0 rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
body a {
|
body a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
@ -8,28 +38,53 @@ body main {
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
body p {
|
||||||
font-family: "Font Awesome 6 Free";
|
margin: 0;
|
||||||
font-style: normal;
|
margin-block: 1em;
|
||||||
font-weight: 400;
|
|
||||||
font-display: block;
|
|
||||||
src: url(fonts/fa-regular-400.woff2) format("woff2"),url(fonts/fa-regular-400.ttf) format("truetype")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
h2, h3 {
|
||||||
font-family: "Font Awesome 6 Free";
|
margin-block-start: 1.5em;
|
||||||
font-style: normal;
|
margin-block-end: 1em;
|
||||||
font-weight: 900;
|
|
||||||
font-display: block;
|
|
||||||
src: url(fonts/fa-solid-900.woff2) format("woff2"),url(fonts/fa-solid-900.ttf) format("truetype")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.essay-image {
|
h4, h5 {
|
||||||
width: 90%;
|
margin-block-start: 1em;
|
||||||
display: block;
|
margin-block-end: 1em;
|
||||||
margin: 1.5rem auto;
|
}
|
||||||
box-shadow: 0 5px 11px 0 rgba(0,0,0,0.18),
|
|
||||||
0 4px 15px 0 rgba(0,0,0,0.15);
|
table {
|
||||||
border-radius: 4px;
|
margin: 0 auto;
|
||||||
background-color: transparent;
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
padding: 3px 20px;
|
||||||
|
border: 1px var(--bs-border-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead {
|
||||||
|
background: var(--bs-secondary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead td {
|
||||||
|
font-weight: 700;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead th {
|
||||||
|
padding: 3px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead tr {
|
||||||
|
border: 1px var(--bs-border-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 0 20px;
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
background-color: var(--bs-primary-bg-subtle);
|
||||||
|
border-block-start: .1em solid var(--bs-primary-border-subtle);
|
||||||
|
border-block-end: .1em solid var(--bs-primary-border-subtle);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import * as fluentUI
|
|
||||||
from '/_content/Microsoft.FluentUI.AspNetCore.Components/Microsoft.FluentUI.AspNetCore.Components.lib.module.js';
|
|
||||||
|
|
||||||
fluentUI.typeRampBaseFontSize.withDefault("16px");
|
|
||||||
fluentUI.typeRampBaseLineHeight.withDefault("16px");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user