Compare commits
2 Commits
master
...
a3791596da
| Author | SHA1 | Date | |
|---|---|---|---|
|
a3791596da
|
|||
|
2be09b8319
|
@@ -12,7 +12,7 @@ indent_style = space
|
|||||||
indent_size = 4
|
indent_size = 4
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[{project.json,appsettings.json,appsettings.*.json}]
|
[project.json]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.{yaml,yml}]
|
[*.{yaml,yml}]
|
||||||
|
|||||||
@@ -8,19 +8,19 @@ jobs:
|
|||||||
runs-on: archlinux
|
runs-on: archlinux
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code.
|
- name: Check out code.
|
||||||
uses: http://github-mirrors.infra.svc.cluster.local/actions/checkout.git@v4
|
uses: https://mirrors.rrricardo.top/actions/checkout.git@v4
|
||||||
with:
|
with:
|
||||||
lfs: true
|
lfs: true
|
||||||
- name: Build project.
|
- name: Build project.
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init
|
|
||||||
podman pull mcr.azure.cn/dotnet/aspnet:10.0
|
podman pull mcr.azure.cn/dotnet/aspnet:10.0
|
||||||
|
cd YaeBlog
|
||||||
pwsh build.ps1 build
|
pwsh build.ps1 build
|
||||||
- name: Workaround to make sure podman-login working.
|
- name: Workaround to make sure podman-login working.
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /root/.docker
|
mkdir /root/.docker
|
||||||
- name: Login tencent cloud docker registry.
|
- name: Login tencent cloud docker registry.
|
||||||
uses: http://github-mirrors.infra.svc.cluster.local/actions/podman-login.git@v1
|
uses: https://mirrors.rrricardo.top/actions/podman-login.git@v1
|
||||||
with:
|
with:
|
||||||
registry: ccr.ccs.tencentyun.com
|
registry: ccr.ccs.tencentyun.com
|
||||||
username: 100044380877
|
username: 100044380877
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -184,7 +184,6 @@ DocProject/Help/html
|
|||||||
|
|
||||||
# Click-Once directory
|
# Click-Once directory
|
||||||
publish/
|
publish/
|
||||||
out/
|
|
||||||
|
|
||||||
# Publish Web Output
|
# Publish Web Output
|
||||||
*.[Pp]ublish.xml
|
*.[Pp]ublish.xml
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
12
YaeBlog.slnx
12
YaeBlog.slnx
@@ -7,17 +7,9 @@
|
|||||||
<File Path=".editorconfig" />
|
<File Path=".editorconfig" />
|
||||||
<File Path=".gitattributes" />
|
<File Path=".gitattributes" />
|
||||||
<File Path=".gitignore" />
|
<File Path=".gitignore" />
|
||||||
<File Path="build.ps1" />
|
|
||||||
<File Path="LICENSE" />
|
<File Path="LICENSE" />
|
||||||
<File Path="README.md" />
|
<File Path="README.md" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/src/">
|
<Project Path="YaeBlog.Tests/YaeBlog.Tests.csproj" />
|
||||||
<Project Path="src/YaeBlog.Tests/YaeBlog.Tests.csproj" />
|
<Project Path="YaeBlog/YaeBlog.csproj" />
|
||||||
<Project Path="src/YaeBlog/YaeBlog.csproj" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/third-party/" />
|
|
||||||
<Folder Name="/third-party/BlazorSvgComponents/" />
|
|
||||||
<Folder Name="/third-party/BlazorSvgComponents/src/">
|
|
||||||
<Project Path="third-party/BlazorSvgComponents/src/BlazorSvgComponents/BlazorSvgComponents.csproj" />
|
|
||||||
</Folder>
|
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
36
YaeBlog/Commands/Binders/BlogOptionsBinder.cs
Normal file
36
YaeBlog/Commands/Binders/BlogOptionsBinder.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.CommandLine.Binding;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using YaeBlog.Models;
|
||||||
|
|
||||||
|
namespace YaeBlog.Commands.Binders;
|
||||||
|
|
||||||
|
public sealed class BlogOptionsBinder : BinderBase<IOptions<BlogOptions>>
|
||||||
|
{
|
||||||
|
protected override IOptions<BlogOptions> GetBoundValue(BindingContext bindingContext)
|
||||||
|
{
|
||||||
|
bindingContext.AddService<IOptions<BlogOptions>>(_ =>
|
||||||
|
{
|
||||||
|
FileInfo settings = new(Path.Combine(Environment.CurrentDirectory, "appsettings.json"));
|
||||||
|
if (!settings.Exists)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to load YaeBlog configurations.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using StreamReader reader = settings.OpenText();
|
||||||
|
using JsonDocument document = JsonDocument.Parse(reader.ReadToEnd());
|
||||||
|
JsonElement root = document.RootElement;
|
||||||
|
JsonElement optionSection = root.GetProperty(BlogOptions.OptionName);
|
||||||
|
|
||||||
|
BlogOptions? result = optionSection.Deserialize<BlogOptions>();
|
||||||
|
if (result is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to load YaeBlog configuration in appsettings.json.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OptionsWrapper<BlogOptions>(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
return bindingContext.GetRequiredService<IOptions<BlogOptions>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
32
YaeBlog/Commands/Binders/EssayScanServiceBinder.cs
Normal file
32
YaeBlog/Commands/Binders/EssayScanServiceBinder.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.CommandLine.Binding;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using YaeBlog.Abstraction;
|
||||||
|
using YaeBlog.Models;
|
||||||
|
using YaeBlog.Services;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
|
namespace YaeBlog.Commands.Binders;
|
||||||
|
|
||||||
|
public sealed class EssayScanServiceBinder : BinderBase<IEssayScanService>
|
||||||
|
{
|
||||||
|
protected override IEssayScanService GetBoundValue(BindingContext bindingContext)
|
||||||
|
{
|
||||||
|
bindingContext.AddService<IEssayScanService>(provider =>
|
||||||
|
{
|
||||||
|
DeserializerBuilder deserializerBuilder = new();
|
||||||
|
deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||||
|
deserializerBuilder.IgnoreUnmatchedProperties();
|
||||||
|
|
||||||
|
SerializerBuilder serializerBuilder = new();
|
||||||
|
serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||||
|
|
||||||
|
IOptions<BlogOptions> options = provider.GetRequiredService<IOptions<BlogOptions>>();
|
||||||
|
ILogger<EssayScanService> logger = provider.GetRequiredService<ILogger<EssayScanService>>();
|
||||||
|
|
||||||
|
return new EssayScanService(serializerBuilder.Build(), deserializerBuilder.Build(), options, logger);
|
||||||
|
});
|
||||||
|
|
||||||
|
return bindingContext.GetRequiredService<IEssayScanService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
YaeBlog/Commands/Binders/ImageCompressServiceBinder.cs
Normal file
21
YaeBlog/Commands/Binders/ImageCompressServiceBinder.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.CommandLine.Binding;
|
||||||
|
using YaeBlog.Abstraction;
|
||||||
|
using YaeBlog.Services;
|
||||||
|
|
||||||
|
namespace YaeBlog.Commands.Binders;
|
||||||
|
|
||||||
|
public sealed class ImageCompressServiceBinder : BinderBase<ImageCompressService>
|
||||||
|
{
|
||||||
|
protected override ImageCompressService GetBoundValue(BindingContext bindingContext)
|
||||||
|
{
|
||||||
|
bindingContext.AddService(provider =>
|
||||||
|
{
|
||||||
|
IEssayScanService essayScanService = provider.GetRequiredService<IEssayScanService>();
|
||||||
|
ILogger<ImageCompressService> logger = provider.GetRequiredService<ILogger<ImageCompressService>>();
|
||||||
|
|
||||||
|
return new ImageCompressService(essayScanService, logger);
|
||||||
|
});
|
||||||
|
|
||||||
|
return bindingContext.GetRequiredService<ImageCompressService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
YaeBlog/Commands/Binders/LoggerBinder.cs
Normal file
18
YaeBlog/Commands/Binders/LoggerBinder.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.CommandLine.Binding;
|
||||||
|
|
||||||
|
namespace YaeBlog.Commands.Binders;
|
||||||
|
|
||||||
|
public sealed class LoggerBinder<T> : BinderBase<ILogger<T>>
|
||||||
|
{
|
||||||
|
protected override ILogger<T> GetBoundValue(BindingContext bindingContext)
|
||||||
|
{
|
||||||
|
bindingContext.AddService(_ => LoggerFactory.Create(builder => builder.AddConsole()));
|
||||||
|
bindingContext.AddService<ILogger<T>>(provider =>
|
||||||
|
{
|
||||||
|
ILoggerFactory factory = provider.GetRequiredService<ILoggerFactory>();
|
||||||
|
return factory.CreateLogger<T>();
|
||||||
|
});
|
||||||
|
|
||||||
|
return bindingContext.GetRequiredService<ILogger<T>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
296
YaeBlog/Commands/YaeBlogCommand.cs
Normal file
296
YaeBlog/Commands/YaeBlogCommand.cs
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
using System.CommandLine;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using YaeBlog.Abstraction;
|
||||||
|
using YaeBlog.Commands.Binders;
|
||||||
|
using YaeBlog.Components;
|
||||||
|
using YaeBlog.Extensions;
|
||||||
|
using YaeBlog.Models;
|
||||||
|
using YaeBlog.Services;
|
||||||
|
|
||||||
|
namespace YaeBlog.Commands;
|
||||||
|
|
||||||
|
public sealed class YaeBlogCommand
|
||||||
|
{
|
||||||
|
private readonly RootCommand _rootCommand = new("YaeBlog Cli");
|
||||||
|
|
||||||
|
public YaeBlogCommand()
|
||||||
|
{
|
||||||
|
AddServeCommand(_rootCommand);
|
||||||
|
AddWatchCommand(_rootCommand);
|
||||||
|
AddListCommand(_rootCommand);
|
||||||
|
AddNewCommand(_rootCommand);
|
||||||
|
AddUpdateCommand(_rootCommand);
|
||||||
|
AddPublishCommand(_rootCommand);
|
||||||
|
AddScanCommand(_rootCommand);
|
||||||
|
AddCompressCommand(_rootCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> RunAsync(string[] args)
|
||||||
|
{
|
||||||
|
return _rootCommand.InvokeAsync(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddServeCommand(RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command serveCommand = new("serve", "Start http server.");
|
||||||
|
rootCommand.AddCommand(serveCommand);
|
||||||
|
|
||||||
|
serveCommand.SetHandler(async context =>
|
||||||
|
{
|
||||||
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
||||||
|
|
||||||
|
builder.Services.AddRazorComponents()
|
||||||
|
.AddInteractiveServerComponents();
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.AddYaeBlog();
|
||||||
|
builder.AddServer();
|
||||||
|
|
||||||
|
WebApplication application = builder.Build();
|
||||||
|
|
||||||
|
application.MapStaticAssets();
|
||||||
|
application.UseAntiforgery();
|
||||||
|
application.UseYaeBlog();
|
||||||
|
|
||||||
|
application.MapRazorComponents<App>()
|
||||||
|
.AddInteractiveServerRenderMode();
|
||||||
|
application.MapControllers();
|
||||||
|
|
||||||
|
CancellationToken token = context.GetCancellationToken();
|
||||||
|
await application.RunAsync(token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddWatchCommand(RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command command = new("watch", "Start a blog watcher that re-render when file changes.");
|
||||||
|
rootCommand.AddCommand(command);
|
||||||
|
|
||||||
|
command.SetHandler(async context =>
|
||||||
|
{
|
||||||
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
||||||
|
|
||||||
|
builder.Services.AddRazorComponents()
|
||||||
|
.AddInteractiveServerComponents();
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.AddYaeBlog();
|
||||||
|
builder.AddWatcher();
|
||||||
|
|
||||||
|
WebApplication application = builder.Build();
|
||||||
|
|
||||||
|
application.MapStaticAssets();
|
||||||
|
application.UseAntiforgery();
|
||||||
|
application.UseYaeBlog();
|
||||||
|
|
||||||
|
application.MapRazorComponents<App>()
|
||||||
|
.AddInteractiveServerRenderMode();
|
||||||
|
application.MapControllers();
|
||||||
|
|
||||||
|
CancellationToken token = context.GetCancellationToken();
|
||||||
|
await application.RunAsync(token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddNewCommand(RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command newCommand = new("new", "Create a new blog file and image directory.");
|
||||||
|
rootCommand.AddCommand(newCommand);
|
||||||
|
|
||||||
|
Argument<string> filenameArgument = new(name: "blog name", description: "The created blog filename.");
|
||||||
|
newCommand.AddArgument(filenameArgument);
|
||||||
|
|
||||||
|
newCommand.SetHandler(async (file, blogOption, _, essayScanService) =>
|
||||||
|
{
|
||||||
|
BlogContents contents = await essayScanService.ScanContents();
|
||||||
|
|
||||||
|
if (contents.Posts.Any(content => content.BlogName == file))
|
||||||
|
{
|
||||||
|
Console.WriteLine("There exists the same title blog in posts.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
},
|
||||||
|
string.Empty, true, [], []));
|
||||||
|
|
||||||
|
Console.WriteLine($"Created new blog '{file}.");
|
||||||
|
}, filenameArgument, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(),
|
||||||
|
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");
|
||||||
|
rootCommand.AddCommand(command);
|
||||||
|
|
||||||
|
command.SetHandler(async (_, _, essyScanService) =>
|
||||||
|
{
|
||||||
|
BlogContents contents = await essyScanService.ScanContents();
|
||||||
|
|
||||||
|
Console.WriteLine($"All {contents.Posts.Count} Posts:");
|
||||||
|
foreach (BlogContent content in contents.Posts.OrderBy(x => x.BlogName))
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - {content.BlogName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"All {contents.Drafts.Count} Drafts:");
|
||||||
|
foreach (BlogContent content in contents.Drafts.OrderBy(x => x.BlogName))
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - {content.BlogName}");
|
||||||
|
}
|
||||||
|
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddScanCommand(RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command command = new("scan", "Scan unused and not found images.");
|
||||||
|
rootCommand.AddCommand(command);
|
||||||
|
|
||||||
|
Option<bool> removeOption =
|
||||||
|
new(name: "--rm", description: "Remove unused images.", getDefaultValue: () => false);
|
||||||
|
command.AddOption(removeOption);
|
||||||
|
|
||||||
|
command.SetHandler(async (_, _, essayScanService, removeOptionValue) =>
|
||||||
|
{
|
||||||
|
BlogContents contents = await essayScanService.ScanContents();
|
||||||
|
List<BlogImageInfo> unusedImages = (from content in contents
|
||||||
|
from image in content.Images
|
||||||
|
where image is { IsUsed: false }
|
||||||
|
select image).ToList();
|
||||||
|
|
||||||
|
if (unusedImages.Count != 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Found unused images:");
|
||||||
|
Console.WriteLine("HINT: use '--rm' to remove unused images.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (BlogImageInfo image in unusedImages)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - {image.File.FullName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeOptionValue)
|
||||||
|
{
|
||||||
|
foreach (BlogImageInfo image in unusedImages)
|
||||||
|
{
|
||||||
|
image.File.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Used not existed images:");
|
||||||
|
|
||||||
|
foreach (BlogContent content in contents)
|
||||||
|
{
|
||||||
|
foreach (FileInfo file in content.NotfoundImages)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"- {file.Name} in {content.BlogName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), removeOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddPublishCommand(RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command command = new("publish", "Publish a new blog file.");
|
||||||
|
rootCommand.AddCommand(command);
|
||||||
|
|
||||||
|
Argument<string> filenameArgument = new(name: "blog name", description: "The published blog filename.");
|
||||||
|
command.AddArgument(filenameArgument);
|
||||||
|
|
||||||
|
command.SetHandler(async (blogOptions, _, essayScanService, filename) =>
|
||||||
|
{
|
||||||
|
BlogContents contents = await essayScanService.ScanContents();
|
||||||
|
|
||||||
|
BlogContent? content = (from blog in contents.Drafts
|
||||||
|
where blog.BlogName == filename
|
||||||
|
select blog).FirstOrDefault();
|
||||||
|
|
||||||
|
if (content is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Target blog does not exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置发布的时间
|
||||||
|
content.Metadata.Date = DateTimeOffset.Now.ToString("o");
|
||||||
|
content.Metadata.UpdateTime = DateTimeOffset.Now.ToString("o");
|
||||||
|
|
||||||
|
// 将选中的博客文件复制到posts
|
||||||
|
await essayScanService.SaveBlogContent(content, isDraft: false);
|
||||||
|
|
||||||
|
// 复制图片文件夹
|
||||||
|
DirectoryInfo sourceImageDirectory =
|
||||||
|
new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName));
|
||||||
|
DirectoryInfo targetImageDirectory =
|
||||||
|
new(Path.Combine(blogOptions.Value.Root, "posts", content.BlogName));
|
||||||
|
|
||||||
|
if (sourceImageDirectory.Exists)
|
||||||
|
{
|
||||||
|
targetImageDirectory.Create();
|
||||||
|
foreach (FileInfo file in sourceImageDirectory.EnumerateFiles())
|
||||||
|
{
|
||||||
|
file.CopyTo(Path.Combine(targetImageDirectory.FullName, file.Name), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceImageDirectory.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除原始的文件
|
||||||
|
FileInfo sourceBlogFile = new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName + ".md"));
|
||||||
|
sourceBlogFile.Delete();
|
||||||
|
}, new BlogOptionsBinder(),
|
||||||
|
new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), filenameArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddCompressCommand(RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command command = new("compress", "Compress png/jpeg image to webp image to reduce size.");
|
||||||
|
rootCommand.Add(command);
|
||||||
|
|
||||||
|
Option<bool> dryRunOption = new("--dry-run", description: "Dry run the compression task but not write.",
|
||||||
|
getDefaultValue: () => false);
|
||||||
|
command.AddOption(dryRunOption);
|
||||||
|
|
||||||
|
command.SetHandler(ImageCommandHandler,
|
||||||
|
new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new LoggerBinder<ImageCompressService>(),
|
||||||
|
new EssayScanServiceBinder(), new ImageCompressServiceBinder(), dryRunOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task ImageCommandHandler(IOptions<BlogOptions> _, ILogger<EssayScanService> _1,
|
||||||
|
ILogger<ImageCompressService> _2,
|
||||||
|
IEssayScanService _3, ImageCompressService imageCompressService, bool dryRun)
|
||||||
|
{
|
||||||
|
await imageCompressService.Compress(dryRun);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta lang="zh-Hans-CN"/>
|
<meta lang="zh-CN"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<base href="/"/>
|
<base href="/"/>
|
||||||
<link rel="stylesheet" href="@Assets["YaeBlog.styles.css"]"/>
|
<link rel="stylesheet" href="@Assets["YaeBlog.styles.css"]"/>
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
@using Microsoft.Extensions.Options
|
|
||||||
@using YaeBlog.Abstraction
|
@using YaeBlog.Abstraction
|
||||||
@using YaeBlog.Models
|
@using YaeBlog.Models
|
||||||
|
|
||||||
@inject IEssayContentService Contents
|
@inject IEssayContentService Contents
|
||||||
@inject IOptions<BlogOptions> Options
|
@inject BlogOptions Options
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="p-10">
|
<div class="p-10">
|
||||||
@@ -44,7 +43,7 @@
|
|||||||
|
|
||||||
<div class="px-6">
|
<div class="px-6">
|
||||||
<p class="text-lg">
|
<p class="text-lg">
|
||||||
@(Options.Value.Announcement)
|
@(Options.Announcement)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -7,13 +7,10 @@
|
|||||||
<Anchor Address="https://dotnet.microsoft.com" Text="@DotnetVersion"/>
|
<Anchor Address="https://dotnet.microsoft.com" Text="@DotnetVersion"/>
|
||||||
驱动。
|
驱动。
|
||||||
</p>
|
</p>
|
||||||
@if (!string.IsNullOrEmpty(BuildCommitId))
|
<p class="text-md">
|
||||||
{
|
Build Commit #
|
||||||
<p class="text-md">
|
<Anchor Address="@BuildCommitUrl" Text="@BuildCommitId"/>
|
||||||
Build Commit #
|
</p>
|
||||||
<Anchor Address="@BuildCommitUrl" Text="@BuildCommitId" NewPage="true"/>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -27,7 +24,7 @@
|
|||||||
{
|
{
|
||||||
private static string DotnetVersion => $".NET {Environment.Version}";
|
private static string DotnetVersion => $".NET {Environment.Version}";
|
||||||
|
|
||||||
private static string? BuildCommitId => Environment.GetEnvironmentVariable("COMMIT_ID");
|
private static string BuildCommitId => Environment.GetEnvironmentVariable("COMMIT_ID") ?? "local_build";
|
||||||
|
|
||||||
private static string BuildCommitUrl => $"https://git.rrricardo.top/jackfiled/YaeBlog/commit/{BuildCommitId}";
|
private static string BuildCommitUrl => $"https://git.rrricardo.top/jackfiled/YaeBlog/commit/{BuildCommitId}";
|
||||||
}
|
}
|
||||||
@@ -24,5 +24,5 @@
|
|||||||
@Body
|
@Body
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Footer/>
|
<Foonter/>
|
||||||
</main>
|
</main>
|
||||||
@@ -32,5 +32,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Footer/>
|
<Foonter/>
|
||||||
</main>
|
</main>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="px-4 py-8 border border-sky-700 rounded-md bg-sky-200">
|
<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 class="flex flex-col gap-3 text-md">
|
||||||
<div>
|
<div>
|
||||||
文章作者:<a href="https://rrricardo.top" target="_blank" class="text-blue-600">初冬的朝阳</a>
|
文章作者:<a href="https://rrricardo.top" target="_blank" class="text-blue-600">Ricardo Ren</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</a>
|
</a>
|
||||||
许可协议,诸位读者如有兴趣可任意转载,不必征询许可,但请注明“转载自
|
许可协议,诸位读者如有兴趣可任意转载,不必征询许可,但请注明“转载自
|
||||||
<a href="https://rrricardo.top/blog/" target="_blank" class="text-blue-600">
|
<a href="https://rrricardo.top/blog/" target="_blank" class="text-blue-600">
|
||||||
Jackfiled's Blog
|
Ricardo's Blog
|
||||||
</a>”。
|
</a>”。
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-4xl page-starter">关于</h1>
|
<h1 class="text-4xl">关于</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-4">
|
<div class="py-4">
|
||||||
@@ -25,23 +25,16 @@
|
|||||||
正在明光村幼儿园附属研究生院攻读计算机科学与技术的硕士学位,研究AI编译器和异构编译器。
|
正在明光村幼儿园附属研究生院攻读计算机科学与技术的硕士学位,研究AI编译器和异构编译器。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="my-1">
|
<p class="my-2">
|
||||||
一般在互联网上使用<span class="italic">初冬的朝阳</span>或者
|
一般在互联网上使用<span class="italic">初冬的朝阳</span>或者<span
|
||||||
<span class="italic">jackfiled</span>的名字活动。
|
class="italic">jackfiled</span>的名字活动。
|
||||||
<span class="line-through">(都是ICP备案过的人了,网名似乎没有太大的用处)</span>
|
<span class="line-through">(都是ICP备案过的人了,网名似乎没有太大的用处)</span>
|
||||||
</p>
|
</p>
|
||||||
<p class="my-1">
|
|
||||||
Fun Fact:<span class="italic">jackfiled</span>这个名字来自于2020年我使用链接在树莓派上的9英寸屏幕注册
|
|
||||||
GitHub的一时兴起,并没有任何特定的含义。
|
|
||||||
<span class="italic">初冬的朝阳</span>则是源自初中,具体典故已不可考。
|
|
||||||
至少到目前为止,还没有在要求唯一ID的平台遇见重名的情况。
|
|
||||||
<span class="line-through">我的真实名字似乎也是如此。</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
<p class="my-1">
|
<p class="my-1">
|
||||||
主要是一个.NET程序员,目前也在尝试写一点Rust。
|
主要是一个C#程序员,目前也在尝试写一点Rust。
|
||||||
<span class="line-through">
|
<span class="line-through">
|
||||||
总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。
|
总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。
|
||||||
</span>
|
</span>
|
||||||
@@ -53,7 +46,7 @@
|
|||||||
常常因为现实的压力而写一些C/C++,现在就在和MLIR殊死搏斗。
|
常常因为现实的压力而写一些C/C++,现在就在和MLIR殊死搏斗。
|
||||||
</p>
|
</p>
|
||||||
<p class="my-1">
|
<p class="my-1">
|
||||||
日常使用Arch Linux,KISS的原则深得我心。
|
日常使用Arch Linux。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -62,7 +55,7 @@
|
|||||||
100%社恐。日常生活是宅在电脑前面自言自语。
|
100%社恐。日常生活是宅在电脑前面自言自语。
|
||||||
</p>
|
</p>
|
||||||
<p class="my-1">
|
<p class="my-1">
|
||||||
兴趣活动是读书和看番,目前在玩戴森球计划和三角洲。2022年~2024年的时候沉迷于原神,现在偶尔还会登上去过一过剧情。
|
兴趣活动是读书和看番,目前在玩戴森球计划和三角洲。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-4xl page-starter">归档</h1>
|
<h1 class="text-4xl">归档</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-4">
|
<div class="py-4">
|
||||||
@@ -6,11 +6,10 @@
|
|||||||
@inject NavigationManager NavigationInstance
|
@inject NavigationManager NavigationInstance
|
||||||
|
|
||||||
<PageTitle>
|
<PageTitle>
|
||||||
Jackfiled's Blog
|
Ricardo's Blog
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="page-starter"></div>
|
|
||||||
<div class="grid grid-cols-4">
|
<div class="grid grid-cols-4">
|
||||||
<div class="col-span-4 md:col-span-3">
|
<div class="col-span-4 md:col-span-3">
|
||||||
@foreach (BlogEssay essay in _essays)
|
@foreach (BlogEssay essay in _essays)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 id="title" class="text-4xl page-starter">@(_essay!.Title)</h1>
|
<h1 id="title" class="text-4xl">@(_essay!.Title)</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row gap-4 py-2">
|
<div class="flex flex-row gap-4 py-2">
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
@page "/friends"
|
@page "/friends"
|
||||||
@using Microsoft.Extensions.Options
|
|
||||||
@using YaeBlog.Models
|
@using YaeBlog.Models
|
||||||
@inject IOptions<BlogOptions> BlogOptionInstance
|
@inject BlogOptions Options
|
||||||
|
|
||||||
<PageTitle>
|
<PageTitle>
|
||||||
友链
|
友链
|
||||||
@@ -9,7 +8,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-4xl page-starter">
|
<h1 class="text-4xl">
|
||||||
友链
|
友链
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-4 g-4 p-2">
|
<div class="grid grid-cols-4 g-4 p-2">
|
||||||
@foreach (FriendLink link in BlogOptionInstance.Value.Links.Where(i => i is not null).Select(i => i!))
|
@foreach (FriendLink link in Options.Links)
|
||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
<a href="@(link.Link)" target="_blank" class="mx-5">
|
<a href="@(link.Link)" target="_blank" class="mx-5">
|
||||||
@@ -44,3 +43,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
@inject IEssayContentService EssayContentInstance
|
@inject IEssayContentService EssayContentInstance
|
||||||
|
|
||||||
<PageTitle>
|
<PageTitle>
|
||||||
Jackfiled's Index
|
Ricardo's Index
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
|
||||||
<div class="mx-14 lg:mx-20">
|
<div class="mx-14 lg:mx-20">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="col-span-3 md:col-span-2">
|
<div class="col-span-3 md:col-span-2">
|
||||||
<div class="flex flex-col gap-y-3 items-center md:items-start md:px-6">
|
<div class="flex flex-col gap-y-3 items-center md:items-start md:px-6">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="text-3xl font-bold page-starter">初冬的朝阳</div>
|
<div class="text-3xl font-bold">初冬的朝阳</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
@@ -57,17 +57,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pt-5 pb-1">
|
<div class="py-5">
|
||||||
<p class="text-lg">恕我不能亲自为您沏茶,还是非常欢迎您来,能在广阔的互联网世界中发现这里实属不易。</p>
|
<p class="text-lg">恕我不能亲自为您沏茶,还是非常欢迎您来,能在广阔的互联网世界中发现这里实属不易。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-lg">
|
<div class="text-lg pt-2">
|
||||||
<p class="py-1">
|
<p class="py-1">
|
||||||
正在攻读计算机科学与技术的硕士学位,研究方向是AI编译和异构编译!
|
正在攻读计算机科学与技术的硕士学位,研究方向是AI编译和异构编译!
|
||||||
喜欢优雅的代码,香甜的蛋糕等等一切可爱的事物。
|
|
||||||
更多的情报请见<Anchor Text="关于" Address="/about/"></Anchor>。
|
|
||||||
</p>
|
</p>
|
||||||
<p class="py-1">
|
<p class="py-1">
|
||||||
|
喜欢优雅的代码,香甜的蛋糕等等一切可爱的事物。
|
||||||
</p>
|
</p>
|
||||||
<p class="py-1">
|
<p class="py-1">
|
||||||
<Anchor Address="/blog/" Text="个人博客"/>中收集了我的各种奇思妙想,如果感兴趣欢迎移步。
|
<Anchor Address="/blog/" Text="个人博客"/>中收集了我的各种奇思妙想,如果感兴趣欢迎移步。
|
||||||
@@ -80,12 +79,9 @@
|
|||||||
</p>
|
</p>
|
||||||
<p class="py-1">
|
<p class="py-1">
|
||||||
日常的代码开发使用自建的<Anchor Text="Gitea" Address="https://git.rrricardo.top" NewPage="@(true)"/>进行,个人
|
日常的代码开发使用自建的<Anchor Text="Gitea" Address="https://git.rrricardo.top" NewPage="@(true)"/>进行,个人
|
||||||
开发的各种项目都可以在上面找到。下面的热力图展示了我在Git上的各种动态<span class="line-through">(Everything as Code)</span>。
|
开发的各种项目都可以在上面找到。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<GitHeatMap/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-3xl page-starter">NotFound!</h3>
|
<h3 class="text-3xl">NotFound!</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="page-starter">
|
<div>
|
||||||
@if (TagName is null)
|
@if (TagName is null)
|
||||||
{
|
{
|
||||||
<h1 class="text-4xl">标签</h1>
|
<h1 class="text-4xl">标签</h1>
|
||||||
@@ -9,6 +9,6 @@
|
|||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)"></RouteView>
|
<RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)"></RouteView>
|
||||||
}
|
}
|
||||||
|
|
||||||
<FocusOnNavigate RouteData="routeData" Selector="page-starter"/>
|
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
||||||
</Found>
|
</Found>
|
||||||
</Router>
|
</Router>
|
||||||
@@ -7,5 +7,3 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using YaeBlog.Components
|
@using YaeBlog.Components
|
||||||
@using BlazorSvgComponents
|
|
||||||
@using BlazorSvgComponents.Models
|
|
||||||
@@ -4,10 +4,8 @@ ARG COMMIT_ID
|
|||||||
ENV COMMIT_ID=${COMMIT_ID}
|
ENV COMMIT_ID=${COMMIT_ID}
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY out/ ./
|
COPY bin/Release/net10.0/publish/ ./
|
||||||
COPY source/ ./source/
|
COPY source/ ./source/
|
||||||
COPY src/YaeBlog/appsettings.json .
|
COPY appsettings.json .
|
||||||
|
|
||||||
ENV BLOG__ROOT="./source"
|
|
||||||
|
|
||||||
ENTRYPOINT ["dotnet", "YaeBlog.dll", "serve"]
|
ENTRYPOINT ["dotnet", "YaeBlog.dll", "serve"]
|
||||||
33
YaeBlog/Extensions/ServiceCollectionExtensions.cs
Normal file
33
YaeBlog/Extensions/ServiceCollectionExtensions.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Markdig;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
|
namespace YaeBlog.Extensions;
|
||||||
|
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddMarkdig(this IServiceCollection collection)
|
||||||
|
{
|
||||||
|
MarkdownPipelineBuilder builder = new();
|
||||||
|
|
||||||
|
builder.UseAdvancedExtensions();
|
||||||
|
|
||||||
|
collection.AddSingleton<MarkdownPipeline>(_ => builder.Build());
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddYamlParser(this IServiceCollection collection)
|
||||||
|
{
|
||||||
|
DeserializerBuilder deserializerBuilder = new();
|
||||||
|
deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||||
|
deserializerBuilder.IgnoreUnmatchedProperties();
|
||||||
|
collection.AddSingleton(deserializerBuilder.Build());
|
||||||
|
|
||||||
|
SerializerBuilder serializerBuilder = new();
|
||||||
|
serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||||
|
collection.AddSingleton(serializerBuilder.Build());
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
YaeBlog/Extensions/WebApplicationBuilderExtensions.cs
Normal file
47
YaeBlog/Extensions/WebApplicationBuilderExtensions.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using AngleSharp;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using YaeBlog.Abstraction;
|
||||||
|
using YaeBlog.Services;
|
||||||
|
using YaeBlog.Models;
|
||||||
|
using YaeBlog.Processors;
|
||||||
|
|
||||||
|
namespace YaeBlog.Extensions;
|
||||||
|
|
||||||
|
public static class WebApplicationBuilderExtensions
|
||||||
|
{
|
||||||
|
public static WebApplicationBuilder AddYaeBlog(this WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.Configure<BlogOptions>(builder.Configuration.GetSection(BlogOptions.OptionName));
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
|
builder.Services.AddMarkdig();
|
||||||
|
builder.Services.AddYamlParser();
|
||||||
|
builder.Services.AddSingleton<AngleSharp.IConfiguration>(_ => Configuration.Default);
|
||||||
|
builder.Services.AddSingleton<IEssayScanService, EssayScanService>();
|
||||||
|
builder.Services.AddSingleton<RendererService>();
|
||||||
|
builder.Services.AddSingleton<IEssayContentService, EssayContentService>();
|
||||||
|
builder.Services.AddTransient<ImagePostRenderProcessor>();
|
||||||
|
builder.Services.AddTransient<HeadlinePostRenderProcessor>();
|
||||||
|
builder.Services.AddTransient<EssayStylesPostRenderProcessor>();
|
||||||
|
builder.Services.AddTransient<BlogOptions>(provider =>
|
||||||
|
provider.GetRequiredService<IOptions<BlogOptions>>().Value);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebApplicationBuilder AddServer(this WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddHostedService<BlogHostedService>();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebApplicationBuilder AddWatcher(this WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddTransient<BlogChangeWatcher>();
|
||||||
|
builder.Services.AddHostedService<BlogHotReloadService>();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
YaeBlog/Models/BlogEssay.cs
Normal file
66
YaeBlog/Models/BlogEssay.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
namespace YaeBlog.Models;
|
||||||
|
|
||||||
|
public class BlogEssay : IComparable<BlogEssay>
|
||||||
|
{
|
||||||
|
public required string Title { get; init; }
|
||||||
|
|
||||||
|
public required string FileName { get; init; }
|
||||||
|
|
||||||
|
public required bool IsDraft { get; init; }
|
||||||
|
|
||||||
|
public required DateTimeOffset PublishTime { get; init; }
|
||||||
|
|
||||||
|
public required DateTimeOffset UpdateTime { get; init; }
|
||||||
|
|
||||||
|
public required string Description { get; init; }
|
||||||
|
|
||||||
|
public required uint WordCount { get; init; }
|
||||||
|
|
||||||
|
public required string ReadTime { get; init; }
|
||||||
|
|
||||||
|
public List<string> Tags { get; } = [];
|
||||||
|
|
||||||
|
public required string HtmlContent { get; init; }
|
||||||
|
|
||||||
|
public string EssayLink => $"/blog/essays/{FileName}";
|
||||||
|
|
||||||
|
public BlogEssay WithNewHtmlContent(string newHtmlContent)
|
||||||
|
{
|
||||||
|
var essay = new BlogEssay
|
||||||
|
{
|
||||||
|
Title = Title,
|
||||||
|
FileName = FileName,
|
||||||
|
IsDraft = IsDraft,
|
||||||
|
PublishTime = PublishTime,
|
||||||
|
UpdateTime = UpdateTime,
|
||||||
|
Description = Description,
|
||||||
|
WordCount = WordCount,
|
||||||
|
ReadTime = ReadTime,
|
||||||
|
HtmlContent = newHtmlContent
|
||||||
|
};
|
||||||
|
essay.Tags.AddRange(Tags);
|
||||||
|
|
||||||
|
return essay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(BlogEssay? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 草稿文章应当排在前面
|
||||||
|
if (IsDraft != other.IsDraft)
|
||||||
|
{
|
||||||
|
return IsDraft ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return other.PublishTime.CompareTo(PublishTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Title}-{PublishTime}";
|
||||||
|
}
|
||||||
|
}
|
||||||
26
YaeBlog/Models/BlogOptions.cs
Normal file
26
YaeBlog/Models/BlogOptions.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace YaeBlog.Models;
|
||||||
|
|
||||||
|
public class BlogOptions
|
||||||
|
{
|
||||||
|
public const string OptionName = "Blog";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 博客markdown文件的根目录
|
||||||
|
/// </summary>
|
||||||
|
public required string Root { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 博客正文的广而告之
|
||||||
|
/// </summary>
|
||||||
|
public required string Announcement { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 博客的起始年份
|
||||||
|
/// </summary>
|
||||||
|
public required int StartYear { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 博客的友链
|
||||||
|
/// </summary>
|
||||||
|
public required List<FriendLink> Links { get; set; }
|
||||||
|
}
|
||||||
27
YaeBlog/Models/FriendLink.cs
Normal file
27
YaeBlog/Models/FriendLink.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace YaeBlog.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 友链模型类
|
||||||
|
/// </summary>
|
||||||
|
public class FriendLink
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 友链名称
|
||||||
|
/// </summary>
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 友链的简单介绍
|
||||||
|
/// </summary>
|
||||||
|
public required string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 友链地址
|
||||||
|
/// </summary>
|
||||||
|
public required string Link { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 头像地址
|
||||||
|
/// </summary>
|
||||||
|
public required string AvatarImage { get; set; }
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
|
|||||||
BeatifyList(document);
|
BeatifyList(document);
|
||||||
BeatifyInlineCode(document);
|
BeatifyInlineCode(document);
|
||||||
|
|
||||||
return essay with { HtmlContent = document.DocumentElement.OuterHtml };
|
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<string, string> _globalCssStyles = new()
|
private readonly Dictionary<string, string> _globalCssStyles = new()
|
||||||
@@ -67,7 +67,7 @@ public class HeadlinePostRenderProcessor(
|
|||||||
logger.LogWarning("Failed to add headline of {}.", essay.FileName);
|
logger.LogWarning("Failed to add headline of {}.", essay.FileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return essay with { HtmlContent = document.DocumentElement.OuterHtml };
|
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BlogHeadline ParserHeadlineElement(IElement element)
|
private static BlogHeadline ParserHeadlineElement(IElement element)
|
||||||
@@ -7,12 +7,6 @@ using YaeBlog.Models;
|
|||||||
|
|
||||||
namespace YaeBlog.Processors;
|
namespace YaeBlog.Processors;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 图片地址路径后处理器
|
|
||||||
/// 将本地图片地址修改为图片API地址
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logger"></param>
|
|
||||||
/// <param name="options"></param>
|
|
||||||
public class ImagePostRenderProcessor(
|
public class ImagePostRenderProcessor(
|
||||||
ILogger<ImagePostRenderProcessor> logger,
|
ILogger<ImagePostRenderProcessor> logger,
|
||||||
IOptions<BlogOptions> options)
|
IOptions<BlogOptions> options)
|
||||||
@@ -40,7 +34,7 @@ public class ImagePostRenderProcessor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return essay with { HtmlContent = html.DocumentElement.OuterHtml };
|
return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => nameof(ImagePostRenderProcessor);
|
public string Name => nameof(ImagePostRenderProcessor);
|
||||||
@@ -65,7 +59,7 @@ public class ImagePostRenderProcessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
string imageLink = "api/files/" + filename;
|
string imageLink = "api/files/" + filename;
|
||||||
logger.LogDebug("Generate image link '{link}' for image file '{filename}'.",
|
logger.LogDebug("Generate image link '{}' for image file '{}'.",
|
||||||
imageLink, filename);
|
imageLink, filename);
|
||||||
|
|
||||||
return imageLink;
|
return imageLink;
|
||||||
4
YaeBlog/Program.cs
Normal file
4
YaeBlog/Program.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
using YaeBlog.Commands;
|
||||||
|
|
||||||
|
YaeBlogCommand command = new();
|
||||||
|
await command.RunAsync(args);
|
||||||
@@ -134,8 +134,7 @@ public partial class EssayScanService : IEssayScanService
|
|||||||
}
|
}
|
||||||
catch (YamlException e)
|
catch (YamlException e)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Failed to parser metadata from {name} due to {exception}, skipping",
|
_logger.LogWarning("Failed to parser metadata from {name} due to {exception}, skipping", blog.BlogFile.Name, e);
|
||||||
blog.BlogFile.Name, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -147,6 +146,7 @@ public partial class EssayScanService : IEssayScanService
|
|||||||
|
|
||||||
private async Task<ImageResult> ScanImagePreBlog(DirectoryInfo directory, string blogName, string content)
|
private async Task<ImageResult> ScanImagePreBlog(DirectoryInfo directory, string blogName, string content)
|
||||||
{
|
{
|
||||||
|
MatchCollection matchResult = ImagePattern.Matches(content);
|
||||||
DirectoryInfo imageDirectory = new(Path.Combine(directory.FullName, blogName));
|
DirectoryInfo imageDirectory = new(Path.Combine(directory.FullName, blogName));
|
||||||
|
|
||||||
Dictionary<string, bool> usedImages = imageDirectory.Exists
|
Dictionary<string, bool> usedImages = imageDirectory.Exists
|
||||||
@@ -154,15 +154,10 @@ public partial class EssayScanService : IEssayScanService
|
|||||||
: [];
|
: [];
|
||||||
List<FileInfo> notFoundImages = [];
|
List<FileInfo> notFoundImages = [];
|
||||||
|
|
||||||
// 同时扫描markdown格式和HTML格式的图片
|
foreach (Match match in matchResult)
|
||||||
MatchCollection markdownMatchResult = MarkdownImagePattern.Matches(content);
|
|
||||||
MatchCollection htmlMatchResult = HtmlImagePattern.Matches(content);
|
|
||||||
|
|
||||||
IEnumerable<string> imageNames = from match in markdownMatchResult.Concat(htmlMatchResult)
|
|
||||||
select match.Groups[1].Value;
|
|
||||||
|
|
||||||
foreach (string imageName in imageNames)
|
|
||||||
{
|
{
|
||||||
|
string imageName = match.Groups[1].Value;
|
||||||
|
|
||||||
// 判断md文件中的图片名称中是否包含文件夹名称
|
// 判断md文件中的图片名称中是否包含文件夹名称
|
||||||
// 例如 blog-1/image.png 或者 image.png
|
// 例如 blog-1/image.png 或者 image.png
|
||||||
// 如果不带文件夹名称
|
// 如果不带文件夹名称
|
||||||
@@ -209,10 +204,7 @@ public partial class EssayScanService : IEssayScanService
|
|||||||
}
|
}
|
||||||
|
|
||||||
[GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
|
[GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
|
||||||
private static partial Regex MarkdownImagePattern { get; }
|
private static partial Regex ImagePattern { get; }
|
||||||
|
|
||||||
[GeneratedRegex("""<img\s[^>]*?src\s*=\s*["']([^"']*)["'][^>]*>""")]
|
|
||||||
private static partial Regex HtmlImagePattern { get; }
|
|
||||||
|
|
||||||
|
|
||||||
private DirectoryInfo ValidateRootDirectory()
|
private DirectoryInfo ValidateRootDirectory()
|
||||||
@@ -34,9 +34,9 @@ public sealed partial class RendererService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<BlogContent> preProcessedContents = await PreProcess(posts);
|
IEnumerable<BlogContent> preProcessedContents = await PreProcess(posts);
|
||||||
ConcurrentBag<BlogEssay> essays = [];
|
|
||||||
|
|
||||||
Parallel.ForEach(preProcessedContents, content =>
|
List<BlogEssay> essays = [];
|
||||||
|
foreach (BlogContent content in preProcessedContents)
|
||||||
{
|
{
|
||||||
(uint wordCount, string readTime) = GetWordCount(content);
|
(uint wordCount, string readTime) = GetWordCount(content);
|
||||||
DateTimeOffset publishDate = content.Metadata.Date is null
|
DateTimeOffset publishDate = content.Metadata.Date is null
|
||||||
@@ -46,20 +46,39 @@ public sealed partial class RendererService(
|
|||||||
DateTimeOffset updateTime = content.Metadata.UpdateTime is null
|
DateTimeOffset updateTime = content.Metadata.UpdateTime is null
|
||||||
? publishDate
|
? publishDate
|
||||||
: DateTimeOffset.Parse(content.Metadata.UpdateTime);
|
: DateTimeOffset.Parse(content.Metadata.UpdateTime);
|
||||||
string description = GetDescription(content);
|
|
||||||
List<string> tags = content.Metadata.Tags ?? [];
|
|
||||||
|
|
||||||
string originalHtml = Markdown.ToHtml(content.Content, markdownPipeline);
|
BlogEssay essay = new()
|
||||||
|
{
|
||||||
|
Title = content.Metadata.Title ?? content.BlogName,
|
||||||
|
FileName = content.BlogName,
|
||||||
|
IsDraft = content.IsDraft,
|
||||||
|
Description = GetDescription(content),
|
||||||
|
WordCount = wordCount,
|
||||||
|
ReadTime = readTime,
|
||||||
|
PublishTime = publishDate,
|
||||||
|
UpdateTime = updateTime,
|
||||||
|
HtmlContent = content.Content
|
||||||
|
};
|
||||||
|
|
||||||
BlogEssay essay = new(
|
if (content.Metadata.Tags is not null)
|
||||||
content.Metadata.Title ?? content.BlogName, content.BlogName, content.IsDraft, publishDate, updateTime,
|
{
|
||||||
description, wordCount, readTime, tags, originalHtml);
|
essay.Tags.AddRange(content.Metadata.Tags);
|
||||||
logger.LogDebug("Render essay: {}", essay);
|
}
|
||||||
|
|
||||||
essays.Add(essay);
|
essays.Add(essay);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcurrentBag<BlogEssay> postProcessEssays = [];
|
||||||
|
Parallel.ForEach(essays, essay =>
|
||||||
|
{
|
||||||
|
BlogEssay newEssay =
|
||||||
|
essay.WithNewHtmlContent(Markdown.ToHtml(essay.HtmlContent, markdownPipeline));
|
||||||
|
|
||||||
|
postProcessEssays.Add(newEssay);
|
||||||
|
logger.LogDebug("Render markdown file {}.", newEssay);
|
||||||
});
|
});
|
||||||
|
|
||||||
IEnumerable<BlogEssay> postProcessedEssays = await PostProcess(essays);
|
IEnumerable<BlogEssay> postProcessedEssays = await PostProcess(postProcessEssays);
|
||||||
|
|
||||||
foreach (BlogEssay essay in postProcessedEssays)
|
foreach (BlogEssay essay in postProcessedEssays)
|
||||||
{
|
{
|
||||||
@@ -1,22 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DotNext" Version="5.26.3" />
|
<PackageReference Include="ImageFlow.NativeRuntime.ubuntu-x86_64" Version="2.1.0-rc11" Condition="$([MSBuild]::IsOsPlatform('Linux'))"/>
|
||||||
<PackageReference Include="ImageFlow.NativeRuntime.ubuntu-x86_64" Version="2.1.0-rc11" Condition="$([MSBuild]::IsOsPlatform('Linux'))" />
|
<PackageReference Include="ImageFlow.NativeRuntime.osx-arm64" Version="2.1.0-rc11" Condition="$([MSBuild]::IsOsPlatform('OSX'))"/>
|
||||||
<PackageReference Include="ImageFlow.NativeRuntime.osx-arm64" Version="2.1.0-rc11" Condition="$([MSBuild]::IsOsPlatform('OSX'))" />
|
<PackageReference Include="ImageFlow.Net" Version="0.13.2"/>
|
||||||
<PackageReference Include="ImageFlow.Net" Version="0.13.2" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1"/>
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
<PackageReference Include="AngleSharp" Version="1.1.0"/>
|
||||||
<PackageReference Include="AngleSharp" Version="1.1.0" />
|
<PackageReference Include="Markdig" Version="0.38.0"/>
|
||||||
<PackageReference Include="Markdig" Version="0.38.0" />
|
<PackageReference Include="YamlDotNet" Version="16.2.1"/>
|
||||||
<PackageReference Include="YamlDotNet" Version="16.2.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="../../third-party/BlazorSvgComponents/src/BlazorSvgComponents/BlazorSvgComponents.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<InternalsVisibleTo Include="YaeBlog.Tests"/>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -27,6 +18,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ClientAssetsRestoreCommand>pnpm install</ClientAssetsRestoreCommand>
|
<ClientAssetsRestoreCommand>pnpm install</ClientAssetsRestoreCommand>
|
||||||
<ClientAssetsBuildCommand>pwsh tailwind.ps1</ClientAssetsBuildCommand>
|
<ClientAssetsBuildCommand>pwsh build.ps1 tailwind</ClientAssetsBuildCommand>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
38
YaeBlog/appsettings.json
Normal file
38
YaeBlog/appsettings.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Tailwind": {
|
||||||
|
"InputFile": "wwwroot/input.css",
|
||||||
|
"OutputFile": "wwwroot/output.css"
|
||||||
|
},
|
||||||
|
"Blog": {
|
||||||
|
"Root": "source",
|
||||||
|
"Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。",
|
||||||
|
"StartYear": 2021,
|
||||||
|
"Links": [
|
||||||
|
{
|
||||||
|
"Name": "Ichirinko",
|
||||||
|
"Description": "这是个大哥",
|
||||||
|
"Link": "https://ichirinko.top",
|
||||||
|
"AvatarImage": "https://ichirinko-blog-img-1.oss-cn-shenzhen.aliyuncs.com/Pic_res/img/202209122110798.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "不会写程序的晨旭",
|
||||||
|
"Description": "一个普通大学生",
|
||||||
|
"Link": "https://chenxutalk.top",
|
||||||
|
"AvatarImage": "https://www.chenxutalk.top/img/photo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "万木长风",
|
||||||
|
"Description": "世界渲染中...",
|
||||||
|
"Link": "https://ryohai.fun",
|
||||||
|
"AvatarImage": "https://ryohai.fun/static/favicons/favicon-32x32.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,31 +3,14 @@
|
|||||||
[cmdletbinding()]
|
[cmdletbinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory = $true, Position = 0, HelpMessage = "Specify the build target")]
|
[Parameter(Mandatory = $true, Position = 0, HelpMessage = "Specify the build target")]
|
||||||
[ValidateSet("publish", "compress", "build", "dev", "new", "watch", "serve")]
|
[ValidateSet("tailwind", "publish", "compress", "build", "dev", "new")]
|
||||||
[string]$Target,
|
[string]$Target,
|
||||||
|
[string]$Output = "wwwroot",
|
||||||
[string]$Essay,
|
[string]$Essay,
|
||||||
[switch]$Compress,
|
[switch]$Compress
|
||||||
[string]$Root = "source"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
begin {
|
begin {
|
||||||
if (($Target -eq "tailwind") -or ($Target -eq "build"))
|
|
||||||
{
|
|
||||||
# Handle tailwind specially.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set the content root.
|
|
||||||
$fullRootPath = Join-Path $(Get-Location) $Root
|
|
||||||
if (-not (Test-Path $fullRootPath))
|
|
||||||
{
|
|
||||||
Write-Error "Content root $fullRootPath not existed."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Use content from" $fullRootPath
|
|
||||||
$env:BLOG__ROOT=$fullRootPath
|
|
||||||
|
|
||||||
Write-Host "Building $Target..."
|
Write-Host "Building $Target..."
|
||||||
|
|
||||||
if ($Target -eq "publish")
|
if ($Target -eq "publish")
|
||||||
@@ -47,9 +30,6 @@ begin {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set to the current location.
|
|
||||||
Push-Location src/YaeBlog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process {
|
process {
|
||||||
@@ -81,11 +61,8 @@ process {
|
|||||||
function Build-Image
|
function Build-Image
|
||||||
{
|
{
|
||||||
$commitId = git rev-parse --short=10 HEAD
|
$commitId = git rev-parse --short=10 HEAD
|
||||||
dotnet publish ./src/YaeBlog/YaeBlog.csproj -o out
|
dotnet publish
|
||||||
Write-Host "Succeed to build blog appliocation."
|
podman build . -t ccr.ccs.tencentyun.com/jackfiled/blog --build-arg COMMIT_ID=$commitId
|
||||||
podman build . -t ccr.ccs.tencentyun.com/jackfiled/blog --build-arg COMMIT_ID=$commitId `
|
|
||||||
-f ./src/YaeBlog/Dockerfile
|
|
||||||
Write-Host "Succeed to build ccr.ccs.tencentyun.com/jackfiled/blog image."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Start-Develop {
|
function Start-Develop {
|
||||||
@@ -110,9 +87,13 @@ process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
switch ($Target)
|
switch ($Target)
|
||||||
{
|
{
|
||||||
|
"tailwind" {
|
||||||
|
Write-Host "Build tailwind css into $Output."
|
||||||
|
pnpm tailwindcss -i wwwroot/tailwind.css -o $Output/tailwind.g.css
|
||||||
|
break
|
||||||
|
}
|
||||||
"publish" {
|
"publish" {
|
||||||
Write-Host "Publish essay $Essay..."
|
Write-Host "Publish essay $Essay..."
|
||||||
dotnet run -- publish $Essay
|
dotnet run -- publish $Essay
|
||||||
@@ -138,21 +119,6 @@ process {
|
|||||||
"new" {
|
"new" {
|
||||||
dotnet run -- new $Essay
|
dotnet run -- new $Essay
|
||||||
}
|
}
|
||||||
"watch" {
|
|
||||||
dotnet run -- watch
|
|
||||||
break
|
|
||||||
}
|
|
||||||
"serve" {
|
|
||||||
dotnet run -- serve
|
|
||||||
break
|
|
||||||
}
|
|
||||||
"list" {
|
|
||||||
dotnet run -- list
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
end {
|
|
||||||
Pop-Location
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user