2 Commits

Author SHA1 Message Date
3053b83bbf feat: a lot of notebooks. 2025-08-31 12:39:18 +08:00
457316971c draft: six notebooks 2025-06-16 00:45:16 +08:00
123 changed files with 279 additions and 1356 deletions

View File

@@ -15,9 +15,6 @@ trim_trailing_whitespace = true
[project.json]
indent_size = 2
[*.{yaml,yml}]
indent_size = 2
# C# and Visual Basic files
[*.{cs,vb}]
charset = utf-8-bom

1
.gitattributes vendored
View File

@@ -1,5 +1,4 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.avif filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,30 +1,33 @@
name: Build blog docker image
on:
push:
branches:
- master
push:
branches:
- master
jobs:
Build-Blog-Image:
runs-on: archlinux
steps:
- name: Check out code.
uses: https://mirrors.rrricardo.top/actions/checkout.git@v4
with:
lfs: true
- name: Build project.
run: |
podman pull mcr.azure.cn/dotnet/aspnet:10.0
cd YaeBlog
pwsh build.ps1 build
- name: Workaround to make sure podman-login working.
run: |
mkdir /root/.docker
- name: Login tencent cloud docker registry.
uses: https://mirrors.rrricardo.top/actions/podman-login.git@v1
with:
registry: ccr.ccs.tencentyun.com
username: 100044380877
password: ${{ secrets.TENCENT_REGISTRY_PASSWORD }}
auth_file_path: /etc/containers/auth.json
- name: Push docker image.
run: podman push ccr.ccs.tencentyun.com/jackfiled/blog:latest
Build-Blog-Image:
runs-on: archlinux
steps:
- uses: https://mirrors.rrricardo.top/actions/checkout.git@v4
name: Check out code
with:
lfs: true
- name: Build project
run: |
cd YaeBlog
dotnet publish
- name: Build docker image
run: |
cd YaeBlog
podman build . -t registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest --build-arg COMMIT_ID=$(git rev-parse --short=10 HEAD)
- name: Workaround to make sure podman login succeed
run: |
mkdir /root/.docker
- name: Login aliyun docker registry
uses: https://mirrors.rrricardo.top/actions/podman-login.git@v1
with:
registry: registry.cn-beijing.aliyuncs.com
username: 初冬的朝阳
password: ${{ secrets.ALIYUN_PASSWORD }}
auth_file_path: /etc/containers/auth.json
- name: Push docker image
run: podman push registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest

View File

@@ -1,13 +0,0 @@
namespace YaeBlog.Tests;
public class DateTimeOffsetTests
{
[Fact]
public void DateTimeOffsetParseTest()
{
const string input = "2026-01-04T16:36:36.5629759+08:00";
DateTimeOffset time = DateTimeOffset.Parse(input);
Assert.Equal("2026年01月04日 16:36:36", time.ToString("yyyy年MM月dd日 HH:mm:ss"));
}
}

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\YaeBlog\YaeBlog.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,6 +10,5 @@
<File Path="LICENSE" />
<File Path="README.md" />
</Folder>
<Project Path="YaeBlog.Tests/YaeBlog.Tests.csproj" />
<Project Path="YaeBlog/YaeBlog.csproj" />
</Solution>

View File

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

View File

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

View File

@@ -5,33 +5,16 @@
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<link rel="stylesheet" href="@Assets["YaeBlog.styles.css"]"/>
<link rel="icon" href="@Assets["images/favicon.ico"]"/>
<link rel="stylesheet" href="@Assets["tailwind.g.css"]"/>
<style>
@@font-face {
font-family: "Font Awesome 6 Free";
font-style: normal;
font-weight: 400;
font-display: block;
src: url(@Assets["fonts/fa-regular-400.woff2"]) format("woff2") url(@Assets["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(@Assets["fonts/fa-solid-900.woff2"]) format("woff2"), url(@Assets["fonts/fa-solid-900.ttf"]) format("truetype")
}
</style>
<link rel="stylesheet" href="YaeBlog.styles.css"/>
<link rel="icon" href="images/favicon.ico"/>
<link rel="stylesheet" href="globals.css"/>
<link rel="stylesheet" href="tailwind.g.css"/>
<HeadOutlet/>
<ImportMap/>
</head>
<body>
<Routes/>
<script src="@Assets["_framework/blazor.web.js"]"></script>
<Routes/>
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@@ -1,29 +0,0 @@
<div class="flex flex-wrap justify-center gap-12 max-w-md md:max-w-lg">
<div class="relative w-40 h-48 md:w-48 md:w-48 overflow-hidden
transition-all duration-300 ease-out hover:scale-125 group">
<img
src="./images/wechat-code.jpeg"
alt="微信赞赏码"
class="w-full h-full object-cover"
/>
<div class="absolute -bottom-8 left-0 right-0 text-center
text-white bg-black opacity-60 text-sm font-medium
backdrop-blur-sm group-hover:bottom-2 transition-all duration-300">
请我喝奶茶<br/>
</div>
</div>
<div class="relative w-40 h-48 md:w-48 md:h-48 overflow-hidden
transition-all duration-300 ease-out hover:scale-125 group">
<img
src="./images/alipay-code.jpeg"
alt="支付宝赞赏码"
class="w-full h-full object-cover"/>
<div class="absolute -bottom-8 left-0 right-0 text-center
text-white bg-black opacity-60 text-sm font-medium
backdrop-blur-sm group-hover:bottom-2 transition-all duration-300">
请我吃晚饭<br/>
</div>
</div>
</div>

View File

@@ -3,7 +3,7 @@
<div class="flex flex-col p-3">
<div class="text-3xl font-bold py-2">
<a href="/blog/essays/@(Essay.FileName)">@(Essay.Title)</a>
<a href="/blog/essays/@(Essay.FileName)" target="_blank">@(Essay.Title)</a>
</div>
<div class="p-2 flex flex-row justify-content-start gap-2">
@@ -14,7 +14,9 @@
@foreach (string key in Essay.Tags)
{
<div class="text-sky-600">
<Anchor Address="@($"/blog/tags/?tagName={UrlEncoder.Default.Encode(key)}")" Text="@($"# {key}")"/>
<a href="/blog/tags/?tagName=@(UrlEncoder.Default.Encode(key))">
# @key
</a>
</div>
}
</div>

View File

@@ -1,28 +0,0 @@
@inherits LayoutComponentBase
@attribute [StreamRendering]
<main class="container mx-auto flex flex-col min-h-screen">
<div class="grid grid-cols-3 mx-3">
<div class="md:col-span-2 col-span-3 h-20 flex items-center">
<a href="/blog/">
<span class="text-blue-600 text-2xl">Ricardo's Blog</span>
</a>
</div>
<div class="md:col-span-1 col-span-3 h-20 flex items-center">
<div class="flex flex-row w-full px-2 md:justify-center justify-end text-xl gap-3">
<Anchor Address="/blog/archives" Text="归档"/>
<Anchor Address="/blog/tags/" Text="标签"/>
<Anchor Address="/about/" Text="关于" NewPage="@(true)"/>
<Anchor Address="/friends" Text="友链" NewPage="@(true)"/>
</div>
</div>
</div>
<div class="px-4 py-2 flex-grow">
@Body
</div>
<Foonter/>
</main>

View File

@@ -1,3 +1,6 @@
@using YaeBlog.Models
@inject BlogOptions Options
<div class="px-4 py-8 border border-sky-700 rounded-md bg-sky-200">
<div class="flex flex-col gap-3 text-md">
<div>
@@ -21,17 +24,6 @@
Ricardo's Blog
</a>”。
</div>
<div class="flex flex-col">
<div class="flex justify-center">
<p>如果觉得不错的话,可以支持一下作者哦~</p>
</div>
<div class="flex justify-center">
<AppreciationCode/>
</div>
</div>
</div>
</div>

View File

@@ -1,53 +0,0 @@
<Project>
<PropertyGroup>
<ClientAssetsRestoreCommand Condition="'$(ClientAssesRestoreCommand)' == ''">pnpm install</ClientAssetsRestoreCommand>
<ClientAssetsBuildCommand Condition="'$(ClientAssetsBuildCommand)' == ''">pnpm run build</ClientAssetsBuildCommand>
<ClientAssetsBuildOutputParameter Condition="'$(ClientAssetsBuildOutputParameter)' == ''">--output</ClientAssetsBuildOutputParameter>
</PropertyGroup>
<PropertyGroup>
<_RestoreClientAssetsBeforeTargets Condition="'$(TargetFramework)' == ''">DispatchToInnerBuilds</_RestoreClientAssetsBeforeTargets>
</PropertyGroup>
<Target Name="RestoreClientAssets" BeforeTargets="$(_RestoreClientAssetsBeforeTargets)">
<Message Importance="high" Text="Running $(ClientAssetsRestoreCommand)"/>
<Exec Command="$(ClientAssetsRestoreCommand)"/>
</Target>
<Target Name="BuildClientAssets" DependsOnTargets="RestoreClientAssets" BeforeTargets="AssignTargetPaths">
<PropertyGroup>
<_ClientAssetsOutputFullPath>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)ClientAssets'))</_ClientAssetsOutputFullPath>
</PropertyGroup>
<MakeDir Directories="$(_ClientAssetsOutputFullPath)"/>
<Exec Command="$(ClientAssetsBuildCommand) $(ClientAssetsBuildOutputParameter) $(_ClientAssetsOutputFullPath)"/>
<ItemGroup>
<_ClientAssetsBuildOutput Include="$(IntermediateOutputPath)ClientAssets\**"/>
</ItemGroup>
</Target>
<Target Name="DefineClientAssets" AfterTargets="BuildClientAssets" DependsOnTargets="ResolveStaticWebAssetsConfiguration">
<ItemGroup>
<FileWrites Include="@(_ClientAssetsBuildOutput)"/>
</ItemGroup>
<DefineStaticWebAssets
CandidateAssets="@(_ClientAssetsBuildOutput)"
SourceId="$(PackageId)"
SourceType="Computed"
ContentRoot="$(_ClientAssetsOutputFullPath)"
BasePath="$(StaticWebAssetBasePath)"
>
<Output TaskParameter="Assets" ItemName="StaticWebAsset"/>
<Output TaskParameter="Assets" ItemName="_ClientAssetsStaticWebAsset"/>
</DefineStaticWebAssets>
<DefineStaticWebAssetEndpoints
CandidateAssets="@(_ClientAssetsStaticWebAsset)"
ContentTypeMappings="@(StaticWebAssetContentTypeMapping)"
>
<Output TaskParameter="Endpoints" ItemName="StaticWebAssetEndpoint" />
</DefineStaticWebAssetEndpoints>
</Target>
</Project>

View File

@@ -1,10 +1,10 @@
FROM mcr.azure.cn/dotnet/aspnet:10.0
FROM mcr.microsoft.com/dotnet/aspnet:9.0
ARG COMMIT_ID
ENV COMMIT_ID=${COMMIT_ID}
WORKDIR /app
COPY bin/Release/net10.0/publish/ ./
COPY bin/Release/net9.0/publish/ ./
COPY source/ ./source/
COPY appsettings.json .

View File

@@ -0,0 +1,44 @@
@inherits LayoutComponentBase
@attribute [StreamRendering]
<main class="container mx-auto flex flex-col min-h-screen">
<div class="grid grid-cols-3 mx-3">
<div class="md:col-span-2 col-span-3 h-20 flex items-center">
<a href="/blog/">
<span class="text-blue-600 text-2xl">Ricardo's Blog</span>
</a>
</div>
<div class="md:col-span-1 col-span-3 h-20 flex items-center">
<div class="flex flex-row w-full px-2 gap-3 md:justify-center justify-end">
<div>
<a href="/blog/archives/">
<span class="text-xl text-blue-600">归档</span>
</a>
</div>
<div>
<a href="/blog/tags/">
<span class="text-xl text-blue-600">标签</span>
</a>
</div>
<div>
<a href="/about/" target="_blank">
<span class="text-xl text-blue-600">关于</span>
</a>
</div>
<div>
<a href="/friends/" target="_blank">
<span class="text-xl text-blue-600">友链</span>
</a>
</div>
</div>
</div>
</div>
<div class="px-4 py-2 flex-grow">
@Body
</div>
<Foonter/>
</main>

View File

@@ -6,7 +6,10 @@ namespace YaeBlog.Models;
public record BlogContents(ConcurrentBag<BlogContent> Drafts, ConcurrentBag<BlogContent> Posts)
: IEnumerable<BlogContent>
{
public IEnumerator<BlogContent> GetEnumerator() => Posts.Concat(Drafts).GetEnumerator();
IEnumerator<BlogContent> IEnumerable<BlogContent>.GetEnumerator()
{
return Posts.Concat(Drafts).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator GetEnumerator() => ((IEnumerable<BlogContent>)this).GetEnumerator();
}

View File

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

View File

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

View File

@@ -19,7 +19,7 @@
</span>
</div>
@foreach (IGrouping<DateTimeOffset, BlogEssay> group in _essays)
@foreach (IGrouping<DateTime, BlogEssay> group in _essays)
{
<div class="p-2">
<div class="flex flex-col">
@@ -30,7 +30,7 @@
<div class="px-4 py-4 flex flex-col">
@foreach (BlogEssay essay in group)
{
<a href="@($"/blog/essays/{essay.FileName}")">
<a target="_blank" href="@($"/blog/essays/{essay.FileName}")">
<div class="flex flex-row p-2 mx-1 rounded-lg hover:bg-gray-300">
<div class="w-20">
@(essay.PublishTime.ToString("MM月dd日"))
@@ -51,13 +51,13 @@
</div>
@code {
private readonly List<IGrouping<DateTimeOffset, BlogEssay>> _essays = [];
private readonly List<IGrouping<DateTime, BlogEssay>> _essays = [];
protected override void OnInitialized()
{
base.OnInitialized();
_essays.AddRange(from essay in Contents.Essays
group essay by new DateTimeOffset(essay.PublishTime.Year, 1, 1,0, 0, 0, TimeSpan.Zero));
group essay by new DateTime(essay.PublishTime.Year, 1, 1));
}
}

View File

@@ -39,11 +39,6 @@
{
_page = Page ?? 1;
_pageCount = Contents.Count / EssaysPerPage + 1;
(_pageCount, int reminder) = int.DivRem(Contents.Count, EssaysPerPage);
if (reminder > 0)
{
_pageCount += 1;
}
if (EssaysPerPage * _page > Contents.Count + EssaysPerPage)
{

View File

@@ -12,42 +12,47 @@
<div class="flex flex-col py-8">
<div>
<div class="flex flex-col items-center">
<div>
<h1 id="title" class="text-4xl">@(_essay!.Title)</h1>
<h1 id="title" class="text-4xl">@(_essay!.Title)</h1>
<div class="col-auto">
</div>
</div>
<div class="px-6 pt-4 pb-2">
<div class="flex flex-row gap-4">
<div class="font-light">
@(_essay!.PublishTime.ToString("yyyy-MM-dd"))
</div>
<div class="flex flex-row gap-4 py-2">
@foreach (string tag in _essay!.Tags)
{
<div class="text-sky-500">
<a href="/blog/tags/?tagName=@(UrlEncoder.Default.Encode(tag))">
# @(tag)
</a>
</div>
}
</div>
<div class="font-light pb-1">
发布于: @(_essay.PublishTime.ToString("yyyy年MM月dd日 HH:mm:ss"))
</div>
@if (_essay.UpdateTime != _essay.PublishTime)
@foreach (string tag in _essay!.Tags)
{
<div class="font-light pb-1">
更新于: @(_essay.UpdateTime.ToString("yyyy年MM月dd日 HH:mm:ss"))
<div class="text-sky-500">
<a href="/blog/tags/?tagName=@(UrlEncoder.Default.Encode(tag))">
# @(tag)
</a>
</div>
}
</div>
</div>
<div class="font-light pb-1">
总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)
</div>
<div class="px-6 pt-2 pb-4">
<div class="font-light">
总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。
</div>
</div>
<div class="grid grid-cols-3">
<div class="col-span-3 md:col-span-2 flex flex-col gap-3">
<div>
@((MarkupString)_essay!.HtmlContent)
</div>
<div>
<LicenseDisclaimer EssayFilename="@BlogKey"/>
</div>
</div>
<div class="col-span-3 md:col-span-1">
<div class="flex flex-col sticky top-20 px-8 pt-20">
<div class="flex flex-col sticky top-0 px-8">
<div>
<h3 class="text-2xl">文章目录</h3>
</div>
@@ -88,17 +93,8 @@
}
</div>
</div>
<div class="col-span-3 md:col-span-2 flex flex-col gap-3">
<div>
@((MarkupString)_essay!.HtmlContent)
</div>
<div>
<LicenseDisclaimer EssayFilename="@BlogKey"/>
</div>
</div>
</div>
</div>
@code {

View File

@@ -7,7 +7,7 @@
<div class="mx-20">
<div class="grid grid-cols-3 py-4">
<div class="col-span-3 md:col-span-1 p-5 p-lg-0">
<img src="@Assets["images/avatar.png"]" alt="Ricardo's Avatar" class="h-auto max-w-full">
<img src="images/avatar.png" alt="Ricardo's Avatar" class="h-auto max-w-full">
</div>
<div class="col-span-3 md:col-span-2">
@@ -26,7 +26,7 @@
<div class="">
<p class="text-lg">
学过一些基础的计算机知识,略懂一些代码
平平无奇的计算机科学与技术学徒,连微小的贡献都没做
</p>
</div>
</div>

View File

@@ -16,7 +16,8 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
public async Task<BlogEssay> ProcessAsync(BlogEssay essay)
{
BrowsingContext context = new(Configuration.Default);
IDocument document = await context.OpenAsync(req => req.Content(essay.HtmlContent));
IDocument document = await context.OpenAsync(
req => req.Content(essay.HtmlContent));
ApplyGlobalCssStyles(document);
BeatifyTable(document);
@@ -35,7 +36,6 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
{ "h5", "text-lg font-bold py-1" },
{ "p", "p-2" },
{ "img", "w-11/12 block mx-auto my-2 rounded-md shadow-md" },
{ "a", "text-blue-600" }
};
private void ApplyGlobalCssStyles(IDocument document)
@@ -102,33 +102,17 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
}
}
/// <summary>
/// 美化各种列表元素
/// </summary>
/// <param name="document"></param>
private static void BeatifyList(IDocument document)
{
foreach (IElement listElement in from e in document.All
where e.LocalName is "ol" or "ul"
foreach (IElement ulElement in from e in document.All
where e.LocalName == "ul"
select e)
{
// 给有序或者无序列表添加不同的样式
listElement.ClassList.Add("ml-10");
switch (listElement.LocalName)
{
case "ul":
{
listElement.ClassList.Add("list-disc");
break;
}
case "ol":
{
listElement.ClassList.Add("list-decimal");
break;
}
}
// 首先给<ul>元素添加样式
ulElement.ClassList.Add("list-disc ml-10");
foreach (IElement liElement in from e in listElement.Children
foreach (IElement liElement in from e in ulElement.Children
where e.LocalName == "li"
select e)
{

View File

@@ -16,11 +16,11 @@ public sealed class BlogHotReloadService(
await rendererService.RenderAsync(true);
Task[] reloadTasks = [WatchFileAsync(stoppingToken)];
Task[] reloadTasks = [FileWatchTask(stoppingToken)];
await Task.WhenAll(reloadTasks);
}
private async Task WatchFileAsync(CancellationToken token)
private async Task FileWatchTask(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
@@ -33,15 +33,6 @@ public sealed class BlogHotReloadService(
break;
}
FileInfo changeFileInfo = new(changeFile);
if (changeFileInfo.Name.StartsWith('.'))
{
// Ignore dot-started file and directory.
logger.LogDebug("Ignore hidden file: {}.", changeFile);
continue;
}
logger.LogInformation("{} changed, re-rendering.", changeFile);
essayContentService.Clear();
await rendererService.RenderAsync(true);

View File

@@ -109,12 +109,6 @@ public partial class EssayScanService : IEssayScanService
{
foreach (BlogResult blog in fileContents)
{
if (blog.BlogContent.Length < 4)
{
// Even not contains a legal header.
continue;
}
int endPos = blog.BlogContent.IndexOf("---", 4, StringComparison.Ordinal);
if (!blog.BlogContent.StartsWith("---") || endPos is -1 or 0)
{
@@ -127,14 +121,14 @@ public partial class EssayScanService : IEssayScanService
try
{
MarkdownMetadata metadata = _yamlDeserializer.Deserialize<MarkdownMetadata>(metadataString);
_logger.LogDebug("Scan metadata title: '{title}' for {name}.", metadata.Title, blog.BlogFile.Name);
_logger.LogDebug("Scan metadata title: '{}' for {}.", metadata.Title, blog.BlogFile.Name);
contents.Add(new BlogContent(blog.BlogFile, metadata, blog.BlogContent[(endPos + 3)..], isDraft,
blog.Images, blog.NotFoundImages));
}
catch (YamlException e)
{
_logger.LogWarning("Failed to parser metadata from {name} due to {exception}, skipping", blog.BlogFile.Name, e);
_logger.LogWarning("Failed to parser metadata from {} due to {}, skipping", blog.BlogFile.Name, e);
}
}
});

View File

@@ -1,62 +0,0 @@
using YaeBlog.Extensions;
using YaeBlog.Models;
namespace YaeBlog.Services
{
public class MarkdownWordCounter
{
private bool _inCodeBlock;
private int _index;
private readonly string _content;
private uint WordCount { get; set; }
private MarkdownWordCounter(BlogContent content)
{
_content = content.Content;
}
private void CountWordInner()
{
while (_index < _content.Length)
{
if (IsCodeBlockTag())
{
_inCodeBlock = !_inCodeBlock;
}
if (!_inCodeBlock && char.IsLetterOrDigit(_content, _index))
{
WordCount += 1;
}
_index++;
}
}
private bool IsCodeBlockTag()
{
// 首先考虑识别代码块
bool outerCodeBlock =
Enumerable.Range(0, 3)
.Select(i => _index + i < _content.Length && _content.AsSpan().Slice(_index + i, 1) is "`")
.All(i => i);
if (outerCodeBlock)
{
return true;
}
// 然后识别行内代码
return _index < _content.Length && _content.AsSpan().Slice(_index, 1) is "`";
}
public static uint CountWord(BlogContent content)
{
MarkdownWordCounter counter = new(content);
counter.CountWordInner();
return counter.WordCount;
}
}
}

View File

@@ -9,7 +9,7 @@ using YaeBlog.Models;
namespace YaeBlog.Services;
public sealed partial class RendererService(
public partial class RendererService(
ILogger<RendererService> logger,
IEssayScanService essayScanService,
MarkdownPipeline markdownPipeline,
@@ -38,15 +38,7 @@ public sealed partial class RendererService(
List<BlogEssay> essays = [];
foreach (BlogContent content in preProcessedContents)
{
(uint wordCount, string readTime) = GetWordCount(content);
DateTimeOffset publishDate = content.Metadata.Date is null
? DateTimeOffset.Now
: DateTimeOffset.Parse(content.Metadata.Date);
// 如果不存在最后的更新时间,就把更新时间设置为发布时间
DateTimeOffset updateTime = content.Metadata.UpdateTime is null
? publishDate
: DateTimeOffset.Parse(content.Metadata.UpdateTime);
uint wordCount = GetWordCount(content);
BlogEssay essay = new()
{
Title = content.Metadata.Title ?? content.BlogName,
@@ -54,9 +46,8 @@ public sealed partial class RendererService(
IsDraft = content.IsDraft,
Description = GetDescription(content),
WordCount = wordCount,
ReadTime = readTime,
PublishTime = publishDate,
UpdateTime = updateTime,
ReadTime = CalculateReadTime(wordCount),
PublishTime = content.Metadata.Date ?? DateTime.Now,
HtmlContent = content.Content
};
@@ -191,21 +182,28 @@ public sealed partial class RendererService(
string description = builder.ToString();
logger.LogDebug("Description of {name} is {desc}.", content.BlogName,
logger.LogDebug("Description of {} is {}.", content.BlogName,
description);
return description;
}
private (uint, string) GetWordCount(BlogContent content)
private uint GetWordCount(BlogContent content)
{
uint count = MarkdownWordCounter.CountWord(content);
int count = (from c in content.Content
where char.IsLetterOrDigit(c)
select c).Count();
logger.LogDebug("Word count of {blog} is {count}", content.BlogName,
logger.LogDebug("Word count of {} is {}", content.BlogName,
count);
// 据说语文教学大纲规定中国高中生阅读现代文的速度是600字每分钟
uint second = count / 10;
TimeSpan span = new(0, 0, (int)second);
return (uint)count;
}
return (count, span.ToString("mm'分'ss'秒'"));
private static string CalculateReadTime(uint wordCount)
{
// 据说语文教学大纲规定中国高中生阅读现代文的速度是600字每分钟
int second = (int)wordCount / 10;
TimeSpan span = new(0, 0, second);
return span.ToString("mm'分 'ss'秒'");
}
}

View File

@@ -11,13 +11,30 @@
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<ClientAssetsRestoreCommand>pnpm install</ClientAssetsRestoreCommand>
<ClientAssetsBuildCommand>pwsh build.ps1 tailwind</ClientAssetsBuildCommand>
</PropertyGroup>
<Target Name="EnsurePnpmInstalled" BeforeTargets="BeforeBuild">
<Message Importance="low" Text="Ensure pnpm is installed..."/>
<Exec Command="pnpm --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
</Exec>
<Error Condition="$(ErrorCode) != 0" Text="Pnpm is not installed which is required for build."/>
<Message Importance="normal" Text="Installing pakages using pnpm..."/>
<Exec Command="pnpm install"/>
</Target>
<Target Name="TailwindGenerate" AfterTargets="EnsurePnpmInstalled" BeforeTargets="BeforeBuild" Condition="'$(_IsPublishing)' == 'yes'">
<Message Importance="normal" Text="Generate css files using tailwind..."/>
<Exec Command="pnpm tailwindcss -i wwwroot/tailwind.css -o $(IntermediateOutputPath)tailwind.g.css"/>
<ItemGroup>
<Content Include="$(IntermediateOutputPath)tailwind.g.css" Visible="false" TargetPath="wwwroot/tailwind.g.css"/>
</ItemGroup>
</Target>
</Project>

View File

@@ -6,4 +6,5 @@
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using YaeBlog
@using YaeBlog.Components

View File

@@ -37,7 +37,7 @@
"Name": "万木长风",
"Description": "世界渲染中...",
"Link": "https://ryohai.fun",
"AvatarImage": "https://ryohai.fun/static/favicons/favicon-32x32.png"
"AvatarImage": "https://ryohai.fun/icon.jpg"
}
]
}

View File

@@ -1,124 +0,0 @@
#!pwsh
[cmdletbinding()]
param(
[Parameter(Mandatory = $true, Position = 0, HelpMessage = "Specify the build target")]
[ValidateSet("tailwind", "publish", "compress", "build", "dev", "new")]
[string]$Target,
[string]$Output = "wwwroot",
[string]$Essay,
[switch]$Compress
)
begin {
Write-Host "Building $Target..."
if ($Target -eq "publish")
{
if ($Essay -eq "")
{
Write-Error "No publish target, please add with --essay argument."
exit 1
}
}
if ($Target -eq "new")
{
if ($Essay -eq "")
{
Write-Error "No new name, please add with --essay argument."
exit 1
}
}
}
process {
function Compress-Image
{
Write-Host "Compress image assets..."
dotnet run -- compress --dry-run
$confirm = Read-Host "Really compress images? (y/n)"
if ($confirm -notmatch "^[yY]$")
{
Write-Host "Not compress images."
return
}
Write-Host "Do compress image..."
dotnet run -- compress
dotnet run -- scan
$confirm = Read-Host "Really delete unused images? (y/n)"
if ($confirm -notmatch "^[yY]$")
{
Write-Host "Not delete images."
return
}
Write-Host "Do delete unused images.."
dotnet run -- scan --rm
}
function Build-Image
{
$commitId = git rev-parse --short=10 HEAD
dotnet publish
podman build . -t ccr.ccs.tencentyun.com/jackfiled/blog --build-arg COMMIT_ID=$commitId
}
function Start-Develop {
Write-Host "Start tailwindcss and dotnet watch servers..."
$pnpmProcess = Start-Process pnpm "tailwindcss -i wwwroot/tailwind.css -o obj/Debug/net10.0/ClientAssets/tailwind.g.css -w" `
-PassThru
try
{
Write-Host "Started pnpm process exit? " $pnpmProcess.HasExited
Start-Process dotnet "watch -- serve" -PassThru | Wait-Process
}
finally
{
if ($pnpmProcess.HasExited)
{
Write-Error "pnpm process has exited!"
exit 1
}
Write-Host "Kill tailwindcss and dotnet watch servers..."
$pnpmProcess | Stop-Process
}
}
switch ($Target)
{
"tailwind" {
Write-Host "Build tailwind css into $Output."
pnpm tailwindcss -i wwwroot/tailwind.css -o $Output/tailwind.g.css
break
}
"publish" {
Write-Host "Publish essay $Essay..."
dotnet run -- publish $Essay
if ($Compress)
{
Compress-Image
}
break
}
"compress" {
Compress-Image
break
}
"build" {
Build-Image
break
}
"dev" {
Start-Develop
break
}
"new" {
dotnet run -- new $Essay
}
}
}

View File

@@ -1,13 +1,13 @@
version: '3.8'
services:
blog:
image: registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.blog.rule=Host(`rrricardo.top`) || Host(`www.rrricardo.top`)"
- "traefik.http.services.blog.loadbalancer.server.port=8080"
- "traefik.http.routers.blog.tls=true"
- "traefik.http.routers.blog.tls.certresolver=myresolver"
- "com.centurylinklabs.watchtower.enable=true"
blog:
image: registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.blog.rule=Host(`rrricardo.top`) || Host(`www.rrricardo.top`)"
- "traefik.http.services.blog.loadbalancer.server.port=8080"
- "traefik.http.routers.blog.tls=true"
- "traefik.http.routers.blog.tls.certresolver=myresolver"
- "com.centurylinklabs.watchtower.enable=true"

View File

@@ -1,13 +1,12 @@
---
title: High Performance Computing 25 SP NVIDIA
date: 2025-08-31T13:50:42.8639950+08:00
date: 2025-04-24T19:02:36.1077330+08:00
tags:
- 高性能计算
- 学习资料
---
Fxxk you, NVIDIA!
<!--more-->

View File

@@ -0,0 +1,10 @@
---
title: High Performance Computing 25 SP Quantum Computing
date: 2025-06-12T19:26:24.6668760+08:00
tags:
- 高性能计算
- 学习资料
---
<!--more-->

View File

@@ -1,13 +1,11 @@
---
title: High Performance Computing 25 SP Non Stored Program Computing
date: 2025-08-31T13:51:17.5260660+08:00
title: High Performance Computing 2025 SP Non Stored Program Computing
date: 2025-05-29T18:29:28.6155560+08:00
tags:
- 高性能计算
- 学习资料
---
No Von Neumann Machines.
<!--more-->
@@ -62,7 +60,7 @@ There are two types of semi-custom ASICs:
The Standard cell based ASICs is also called as **Cell-based ASIC(CBIC)**.
![image-20250815093113115](./hpc-2025-non-stored-program-computing/image-20250815093113115.webp)
![image-20250815093113115](./hpc-2025-non-stored-program-computing/image-20250815093113115.png)
> The *gate* is used a unit to measure the ability of semiconductor to store logical elements.
@@ -86,7 +84,7 @@ Depending on the structure, the standard PLD can be divided into:
- Programmable Logic Array(PLA): A programmable array of AND gates feeding a programmable of OR gates.
- Complex Programmable Logic Device(CPLD) and Field Programmable Gate Array(FPGA): complex enough to be called as *architecture*.
![image-20250817183832472](./hpc-2025-non-stored-program-computing/image-20250817183832472.webp)
![image-20250817183832472](./hpc-2025-non-stored-program-computing/image-20250817183832472.png)
@@ -98,7 +96,7 @@ Depending on the structure, the standard PLD can be divided into:
### FPGA Architecture
![image-20250817184419856](./hpc-2025-non-stored-program-computing/image-20250817184419856.webp)
![image-20250817184419856](./hpc-2025-non-stored-program-computing/image-20250817184419856.png)
#### Configurable Logic Block(CLB) Architecture
@@ -118,7 +116,7 @@ LUT is a ram with data width of 1 bit and the content is programmed at power up.
The below figure shows LUT working:
![image-20250817185111521](./hpc-2025-non-stored-program-computing/image-20250817185111521.webp)
![image-20250817185111521](./hpc-2025-non-stored-program-computing/image-20250817185111521.png)
The configuration memory holds the output of truth table entries, so that when the FPGA is restarting it will run with the same *program*.
@@ -128,7 +126,7 @@ And as the truth table entries are just bits, the program of FPGA is called as *
Let the input signal as address, the LUT will be configured as a RAM. Normally, LUT mode performs read operations, the address decoders can generate clock signal to latches for writing operation.
![image-20250817185859510](./hpc-2025-non-stored-program-computing/image-20250817185859510.webp)
![image-20250817185859510](./hpc-2025-non-stored-program-computing/image-20250817185859510.png)
#### Routing Architecture
@@ -136,7 +134,7 @@ The logic blocks are connected to each though programmable routing network. And
Horizontal and vertical mesh or wire segments interconnection by programmable switches called programmable interconnect points(PIPs).
![image-20250817192006784](./hpc-2025-non-stored-program-computing/image-20250817192006784.webp)
![image-20250817192006784](./hpc-2025-non-stored-program-computing/image-20250817192006784.png)
These PIPs are implemented using a transmission gate controlled by a memory bits from the configuration memory.
@@ -148,7 +146,7 @@ Several types of PIPs are used in the FPGA:
- Non-decoded MUX: n wire segments each with a configuration bit.
- Compound cross-point: 6 breakpoint PIPs and can isolate two isolated signal nets.
![image-20250817194355228](./hpc-2025-non-stored-program-computing/image-20250817194355228.webp)
![image-20250817194355228](./hpc-2025-non-stored-program-computing/image-20250817194355228.png)
#### Input/Output Architecture
@@ -160,7 +158,7 @@ The programmable Input/Output cells consists of three parts:
- Routing resources.
- Programmable I/O voltage and current levels.
![image-20250817195139631](./hpc-2025-non-stored-program-computing/image-20250817195139631.webp)
![image-20250817195139631](./hpc-2025-non-stored-program-computing/image-20250817195139631.png)
#### Fine-grained and Coarse-grained Architecture
@@ -188,9 +186,9 @@ Three types of interconnected devices have been commonly used to connect there w
### FPGA Design Flow
![image-20250817195714935](./hpc-2025-non-stored-program-computing/image-20250817195714935.webp)
![image-20250817195714935](./hpc-2025-non-stored-program-computing/image-20250817195714935.png)
![image-20250817200350750](./hpc-2025-non-stored-program-computing/image-20250817200350750.webp)
![image-20250817200350750](./hpc-2025-non-stored-program-computing/image-20250817200350750.png)
The FPGA configuration techniques contains:
@@ -224,7 +222,7 @@ The OpenCL is not an traditional hardare description language. And OpenCL needs
The follow figure shows how the OpenCL-FPGA compiler turns an vector adding function into the circuit.
![image-20250829210329225](./hpc-2025-non-stored-program-computing/image-20250829210329225.webp)
![image-20250829210329225](./hpc-2025-non-stored-program-computing/image-20250829210329225.png)
The compiler generates three stages for this function:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,12 +1,11 @@
---
title: High Performance Computing 25 SP OpenCL Programming
date: 2025-08-31T13:51:02.0181970+08:00
title: High Performance Computing 2025 SP OpenCL Programming
date: 2025-05-29T18:29:14.8444660+08:00
tags:
- 高性能计算
- 学习资料
---
Open Computing Language.
<!--more-->

View File

@@ -1,12 +1,11 @@
---
title: High Performance Computing 25 SP Potpourri
date: 2025-08-31T13:51:29.8809980+08:00
date: 2025-06-12T18:45:49.2698190+08:00
tags:
- 高性能计算
- 学习资料
---
Potpourri has a good taste.
<!--more-->

View File

@@ -1,12 +1,11 @@
---
title: High Performance Computing 25 SP Programming CUDA
date: 2025-08-31T13:50:53.6891520+08:00
title: High Performance Computing 2025 SP Programming CUDA
date: 2025-05-15T19:13:48.8893010+08:00
tags:
- 高性能计算
- 学习资料
---
Compute Unified Device Architecture
<!--more-->

Some files were not shown because too many files have changed in this diff Show More