diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7cdbe0b --- /dev/null +++ b/LICENSE @@ -0,0 +1,438 @@ +Attribution-NonCommercial-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e4fe70 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# YaeBlog + +Ricardo Ren 的个人主页和博客仓库。 + +主页地址:[rrricardo.top](https://rrricardo.top) + +博客地址:[rrricardo.top/blog/](https://rrricardo.top/blog/) + +欢迎各位来玩~ 未来也会添加更多有趣的功能~ diff --git a/YaeBlog.Core/Abstractions/IEssayContentService.cs b/YaeBlog.Core/Abstractions/IEssayContentService.cs index 3fbbb42..9be561c 100644 --- a/YaeBlog.Core/Abstractions/IEssayContentService.cs +++ b/YaeBlog.Core/Abstractions/IEssayContentService.cs @@ -9,5 +9,14 @@ public interface IEssayContentService public IReadOnlyDictionary> Tags { get; } - public bool SearchByUrlEncodedTag(string tag,[NotNullWhen(true)] out List? result); + public IReadOnlyDictionary Headlines { get; } + + public bool TryAddHeadline(string filename, BlogHeadline headline); + public bool SearchByUrlEncodedTag(string tag, [NotNullWhen(true)] out List? result); + + public bool TryAdd(BlogEssay essay); + + public void RefreshTags(); + + public void Clear(); } diff --git a/YaeBlog.Core/Abstractions/IEssayScanService.cs b/YaeBlog.Core/Abstractions/IEssayScanService.cs new file mode 100644 index 0000000..56957c8 --- /dev/null +++ b/YaeBlog.Core/Abstractions/IEssayScanService.cs @@ -0,0 +1,10 @@ +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Abstractions; + +public interface IEssayScanService +{ + public Task ScanContents(); + + public Task SaveBlogContent(BlogContent content, bool isDraft = true); +} diff --git a/YaeBlog.Core/Abstractions/ITableOfContentService.cs b/YaeBlog.Core/Abstractions/ITableOfContentService.cs deleted file mode 100644 index 08b997c..0000000 --- a/YaeBlog.Core/Abstractions/ITableOfContentService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using YaeBlog.Core.Models; - -namespace YaeBlog.Core.Abstractions; - -public interface ITableOfContentService -{ - public IReadOnlyDictionary Headlines { get; } -} diff --git a/YaeBlog.Core/Extensions/ServiceCollectionExtensions.cs b/YaeBlog.Core/Extensions/ServiceCollectionExtensions.cs index 2b617fa..ab9a02e 100644 --- a/YaeBlog.Core/Extensions/ServiceCollectionExtensions.cs +++ b/YaeBlog.Core/Extensions/ServiceCollectionExtensions.cs @@ -20,12 +20,14 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddYamlParser(this IServiceCollection collection) { - DeserializerBuilder builder = new(); + DeserializerBuilder deserializerBuilder = new(); + deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); + deserializerBuilder.IgnoreUnmatchedProperties(); + collection.AddSingleton(deserializerBuilder.Build()); - builder.WithNamingConvention(CamelCaseNamingConvention.Instance); - builder.IgnoreUnmatchedProperties(); - - collection.AddSingleton(_ => builder.Build()); + SerializerBuilder serializerBuilder = new(); + serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); + collection.AddSingleton(serializerBuilder.Build()); return collection; } diff --git a/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs b/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs index d469a33..f6cd91a 100644 --- a/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs +++ b/YaeBlog.Core/Extensions/WebApplicationBuilderExtensions.cs @@ -20,14 +20,9 @@ public static class WebApplicationBuilderExtensions builder.Services.AddMarkdig(); builder.Services.AddYamlParser(); builder.Services.AddSingleton(_ => Configuration.Default); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(provider => - provider.GetRequiredService()); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(provider => - provider.GetRequiredService()); + builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); @@ -35,8 +30,21 @@ public static class WebApplicationBuilderExtensions builder.Services.AddTransient(provider => provider.GetRequiredService>().Value); + return builder; + } + + public static WebApplicationBuilder AddServer(this WebApplicationBuilder builder) + { builder.Services.AddHostedService(); return builder; } + + public static WebApplicationBuilder AddWatcher(this WebApplicationBuilder builder) + { + builder.Services.AddTransient(); + builder.Services.AddHostedService(); + + return builder; + } } diff --git a/YaeBlog.Core/Models/BlogContent.cs b/YaeBlog.Core/Models/BlogContent.cs index f7999d6..4cf6c22 100644 --- a/YaeBlog.Core/Models/BlogContent.cs +++ b/YaeBlog.Core/Models/BlogContent.cs @@ -4,5 +4,7 @@ public class BlogContent { public required string FileName { get; init; } + public required MarkdownMetadata Metadata { get; init; } + public required string FileContent { get; set; } } diff --git a/YaeBlog.Core/Models/BlogContents.cs b/YaeBlog.Core/Models/BlogContents.cs new file mode 100644 index 0000000..9514903 --- /dev/null +++ b/YaeBlog.Core/Models/BlogContents.cs @@ -0,0 +1,10 @@ +using System.Collections.Concurrent; + +namespace YaeBlog.Core.Models; + +public sealed class BlogContents(ConcurrentBag drafts, ConcurrentBag posts) +{ + public ConcurrentBag Drafts { get; } = drafts; + + public ConcurrentBag Posts { get; } = posts; +} diff --git a/YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs b/YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs index ddfed50..74dd5d3 100644 --- a/YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs +++ b/YaeBlog.Core/Processors/HeadlinePostRenderProcessor.cs @@ -1,14 +1,15 @@ using AngleSharp; using AngleSharp.Dom; +using Microsoft.Extensions.Logging; using YaeBlog.Core.Abstractions; using YaeBlog.Core.Models; -using YaeBlog.Core.Services; namespace YaeBlog.Core.Processors; public class HeadlinePostRenderProcessor( IConfiguration angleConfiguration, - TableOfContentService tableOfContentService) : IPostRenderProcessor + IEssayContentService essayContentService, + ILogger logger) : IPostRenderProcessor { public async Task ProcessAsync(BlogEssay essay) { @@ -62,7 +63,10 @@ public class HeadlinePostRenderProcessor( FindParentHeadline(topHeadline, level2List).Children.AddRange(level3List); topHeadline.Children.AddRange(level2List); - tableOfContentService.AddHeadline(essay.FileName, topHeadline); + if (!essayContentService.TryAddHeadline(essay.FileName, topHeadline)) + { + logger.LogWarning("Failed to add headline of {}.", essay.FileName); + } return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml); } diff --git a/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs b/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs index 0592899..e0f3ab0 100644 --- a/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs +++ b/YaeBlog.Core/Processors/ImagePostRenderProcessor.cs @@ -3,6 +3,7 @@ using AngleSharp.Dom; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using YaeBlog.Core.Abstractions; +using YaeBlog.Core.Exceptions; using YaeBlog.Core.Models; namespace YaeBlog.Core.Processors; @@ -47,12 +48,12 @@ public class ImagePostRenderProcessor(ILogger logger, filename = Path.Combine(essayFilename, filename); } - filename = Path.Combine(_options.Root, filename); + filename = Path.Combine(_options.Root, "posts", filename); if (!Path.Exists(filename)) { logger.LogError("Failed to found image: {}.", filename); - throw new InvalidOperationException(); + throw new BlogFileException($"Image {filename} doesn't exist."); } string imageLink = "api/files/" + filename; diff --git a/YaeBlog.Core/README.md b/YaeBlog.Core/README.md deleted file mode 100644 index 20e3510..0000000 --- a/YaeBlog.Core/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# YaeBlog.Core - -A blog generation totally based on Blazor. - -You can using this to create your blog all in .NET stack! diff --git a/YaeBlog.Core/Services/BlogChangeWatcher.cs b/YaeBlog.Core/Services/BlogChangeWatcher.cs new file mode 100644 index 0000000..a6d4136 --- /dev/null +++ b/YaeBlog.Core/Services/BlogChangeWatcher.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Services; + +public sealed class BlogChangeWatcher : IDisposable +{ + private readonly FileSystemWatcher _fileSystemWatcher; + private readonly ILogger _logger; + + public BlogChangeWatcher(IOptions options, ILogger logger) + { + _logger = logger; + _fileSystemWatcher = new FileSystemWatcher(Path.Combine(Environment.CurrentDirectory, options.Value.Root)); + + _fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | + NotifyFilters.DirectoryName | NotifyFilters.Size; + _fileSystemWatcher.IncludeSubdirectories = true; + _fileSystemWatcher.EnableRaisingEvents = true; + } + + public async Task WaitForChange(CancellationToken cancellationToken = default) + { + TaskCompletionSource tcs = new(); + cancellationToken.Register(() => tcs.TrySetResult(null)); + + _logger.LogDebug("Register file change handle."); + _fileSystemWatcher.Changed += FileChangedCallback; + _fileSystemWatcher.Created += FileChangedCallback; + _fileSystemWatcher.Deleted += FileChangedCallback; + _fileSystemWatcher.Renamed += FileChangedCallback; + + string? result; + try + { + result = await tcs.Task; + } + finally + { + _logger.LogDebug("Unregister file change handle."); + _fileSystemWatcher.Changed -= FileChangedCallback; + _fileSystemWatcher.Created -= FileChangedCallback; + _fileSystemWatcher.Deleted -= FileChangedCallback; + _fileSystemWatcher.Renamed -= FileChangedCallback; + } + + return result; + + void FileChangedCallback(object _, FileSystemEventArgs e) + { + _logger.LogDebug("File {} change detected.", e.Name); + tcs.TrySetResult(e.Name); + } + } + + public void Dispose() + { + _fileSystemWatcher.Dispose(); + } +} diff --git a/YaeBlog.Core/Services/BlogHotReloadService.cs b/YaeBlog.Core/Services/BlogHotReloadService.cs new file mode 100644 index 0000000..f112e10 --- /dev/null +++ b/YaeBlog.Core/Services/BlogHotReloadService.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using YaeBlog.Core.Abstractions; + +namespace YaeBlog.Core.Services; + +public sealed class BlogHotReloadService( + RendererService rendererService, + IEssayContentService essayContentService, + BlogChangeWatcher watcher, + ILogger logger) : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + logger.LogInformation("BlogHotReloadService is starting."); + + await rendererService.RenderAsync(); + + while (!stoppingToken.IsCancellationRequested) + { + logger.LogDebug("Watching file changes..."); + string? changFile = await watcher.WaitForChange(stoppingToken); + + if (changFile is null) + { + logger.LogInformation("BlogHotReloadService is stopping."); + break; + } + + logger.LogInformation("{} changed, re-rendering.", changFile); + essayContentService.Clear(); + await rendererService.RenderAsync(); + } + } +} diff --git a/YaeBlog.Core/Services/EssayContentService.cs b/YaeBlog.Core/Services/EssayContentService.cs index aa800f2..05f49b6 100644 --- a/YaeBlog.Core/Services/EssayContentService.cs +++ b/YaeBlog.Core/Services/EssayContentService.cs @@ -11,12 +11,18 @@ public class EssayContentService : IEssayContentService private readonly Dictionary> _tags = []; + private readonly ConcurrentDictionary _headlines = new(); + public bool TryAdd(BlogEssay essay) => _essays.TryAdd(essay.FileName, essay); + public bool TryAddHeadline(string filename, BlogHeadline headline) => _headlines.TryAdd(filename, headline); + public IReadOnlyDictionary Essays => _essays; public IReadOnlyDictionary> Tags => _tags; + public IReadOnlyDictionary Headlines => _headlines; + public void RefreshTags() { _tags.Clear(); @@ -45,4 +51,11 @@ public class EssayContentService : IEssayContentService return result is not null; } + + public void Clear() + { + _essays.Clear(); + _tags.Clear(); + _headlines.Clear(); + } } diff --git a/YaeBlog.Core/Services/EssayScanService.cs b/YaeBlog.Core/Services/EssayScanService.cs index b486c8f..5ac4036 100644 --- a/YaeBlog.Core/Services/EssayScanService.cs +++ b/YaeBlog.Core/Services/EssayScanService.cs @@ -1,20 +1,104 @@ using System.Collections.Concurrent; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using YaeBlog.Core.Abstractions; using YaeBlog.Core.Exceptions; using YaeBlog.Core.Models; +using YamlDotNet.Core; +using YamlDotNet.Serialization; namespace YaeBlog.Core.Services; public class EssayScanService( + ISerializer yamlSerializer, + IDeserializer yamlDeserializer, IOptions blogOptions, - ILogger logger) + ILogger logger) : IEssayScanService { private readonly BlogOptions _blogOptions = blogOptions.Value; - public async Task> ScanAsync() + public async Task ScanContents() { - string root = Path.Combine(Environment.CurrentDirectory, _blogOptions.Root); + ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts); + + return new BlogContents( + await ScanContentsInternal(drafts), + await ScanContentsInternal(posts)); + } + + public async Task SaveBlogContent(BlogContent content, bool isDraft = true) + { + ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts); + + FileInfo targetFile = isDraft + ? new FileInfo(Path.Combine(drafts.FullName, content.FileName + ".md")) + : new FileInfo(Path.Combine(posts.FullName, content.FileName + ".md")); + + if (targetFile.Exists) + { + logger.LogWarning("Blog {} exists, overriding.", targetFile.Name); + } + + await using StreamWriter writer = targetFile.CreateText(); + + await writer.WriteAsync("---\n"); + await writer.WriteAsync(yamlSerializer.Serialize(content.Metadata)); + await writer.WriteAsync("---\n"); + await writer.WriteAsync("\n"); + } + + private async Task> ScanContentsInternal(DirectoryInfo directory) + { + IEnumerable markdownFiles = from file in directory.EnumerateFiles() + where file.Extension == ".md" + select file; + + ConcurrentBag<(string, string)> fileContents = []; + + await Parallel.ForEachAsync(markdownFiles, async (file, token) => + { + using StreamReader reader = file.OpenText(); + fileContents.Add((file.Name, await reader.ReadToEndAsync(token))); + }); + + ConcurrentBag contents = []; + + await Task.Run(() => + { + foreach ((string filename, string content) in fileContents) + { + int endPos = content.IndexOf("---", 4, StringComparison.Ordinal); + if (!content.StartsWith("---") || endPos is -1 or 0) + { + logger.LogWarning("Failed to parse metadata from {}, skipped.", filename); + return; + } + + string metadataString = content[4..endPos]; + + try + { + MarkdownMetadata metadata = yamlDeserializer.Deserialize(metadataString); + logger.LogDebug("Scan metadata title: '{}' for {}.", metadata.Title, filename); + + contents.Add(new BlogContent + { + FileName = filename[..^3], Metadata = metadata, FileContent = content[(endPos + 3)..] + }); + } + catch (YamlException e) + { + logger.LogWarning("Failed to parser metadata from {} due to {}, skipping", filename, e); + } + } + }); + + return contents; + } + + private void ValidateDirectory(string root, out DirectoryInfo drafts, out DirectoryInfo posts) + { + root = Path.Combine(Environment.CurrentDirectory, root); DirectoryInfo rootDirectory = new(root); if (!rootDirectory.Exists) @@ -22,36 +106,17 @@ public class EssayScanService( throw new BlogFileException($"'{root}' is not a directory."); } - List markdownFiles = []; - - await Task.Run(() => + if (rootDirectory.EnumerateDirectories().All(dir => dir.Name != "drafts")) { - foreach (FileInfo fileInfo in rootDirectory.EnumerateFiles()) - { - if (fileInfo.Extension != ".md") - { - continue; - } + throw new BlogFileException($"'{root}/drafts' not exists."); + } - logger.LogDebug("Scan markdown file: {}.", fileInfo.Name); - markdownFiles.Add(fileInfo); - } - }); - - ConcurrentBag contents = []; - - await Parallel.ForEachAsync(markdownFiles, async (info, token) => + if (rootDirectory.EnumerateDirectories().All(dir => dir.Name != "posts")) { - StreamReader reader = new(info.OpenRead()); + throw new BlogFileException($"'{root}/posts' not exists."); + } - BlogContent content = new() - { - FileName = info.Name.Split('.')[0], FileContent = await reader.ReadToEndAsync(token) - }; - - contents.Add(content); - }); - - return contents.ToList(); + drafts = new DirectoryInfo(Path.Combine(root, "drafts")); + posts = new DirectoryInfo(Path.Combine(root, "posts")); } } diff --git a/YaeBlog.Core/Services/RendererService.cs b/YaeBlog.Core/Services/RendererService.cs index e5b1d96..8ed7103 100644 --- a/YaeBlog.Core/Services/RendererService.cs +++ b/YaeBlog.Core/Services/RendererService.cs @@ -7,17 +7,14 @@ using Microsoft.Extensions.Logging; using YaeBlog.Core.Abstractions; using YaeBlog.Core.Exceptions; using YaeBlog.Core.Models; -using YamlDotNet.Core; -using YamlDotNet.Serialization; namespace YaeBlog.Core.Services; public partial class RendererService( ILogger logger, - EssayScanService essayScanService, + IEssayScanService essayScanService, MarkdownPipeline markdownPipeline, - IDeserializer yamlDeserializer, - EssayContentService essayContentService) + IEssayContentService essayContentService) { private readonly Stopwatch _stopwatch = new(); @@ -30,30 +27,30 @@ public partial class RendererService( _stopwatch.Start(); logger.LogInformation("Render essays start."); - List contents = await essayScanService.ScanAsync(); - IEnumerable preProcessedContents = await PreProcess(contents); + BlogContents contents = await essayScanService.ScanContents(); + List posts = contents.Posts.ToList(); + IEnumerable preProcessedContents = await PreProcess(posts); List essays = []; await Task.Run(() => { foreach (BlogContent content in preProcessedContents) { - MarkdownMetadata? metadata = TryParseMetadata(content); uint wordCount = GetWordCount(content); BlogEssay essay = new() { - Title = metadata?.Title ?? content.FileName, + Title = content.Metadata.Title ?? content.FileName, FileName = content.FileName, Description = GetDescription(content), WordCount = wordCount, ReadTime = CalculateReadTime(wordCount), - PublishTime = metadata?.Date ?? DateTime.Now, + PublishTime = content.Metadata.Date ?? DateTime.Now, HtmlContent = content.FileContent }; - if (metadata?.Tags is not null) + if (content.Metadata.Tags is not null) { - essay.Tags.AddRange(metadata.Tags); + essay.Tags.AddRange(content.Metadata.Tags); } essays.Add(essay); @@ -138,45 +135,6 @@ public partial class RendererService( }); } - private MarkdownMetadata? TryParseMetadata(BlogContent content) - { - string fileContent = content.FileContent.Trim(); - - if (!fileContent.StartsWith("---")) - { - return null; - } - - // 移除起始的--- - fileContent = fileContent[3..]; - - int lastPos = fileContent.IndexOf("---", StringComparison.Ordinal); - if (lastPos is -1 or 0) - { - return null; - } - - string yamlContent = fileContent[..lastPos]; - // 返回去掉元数据之后的文本 - lastPos += 3; - content.FileContent = fileContent[lastPos..]; - - try - { - MarkdownMetadata metadata = - yamlDeserializer.Deserialize(yamlContent); - logger.LogDebug("Title: {}, Publish Date: {}.", - metadata.Title, metadata.Date); - - return metadata; - } - catch (YamlException e) - { - logger.LogWarning("Failed to parse '{}' metadata: {}", yamlContent, e); - return null; - } - } - [GeneratedRegex(@"(? _headlines = []; - - public IReadOnlyDictionary Headlines => _headlines; - - public void AddHeadline(string filename, BlogHeadline headline) - { - if (!_headlines.TryAdd(filename, headline)) - { - throw new InvalidOperationException(); - } - } -} diff --git a/YaeBlog.Core/YaeBlog.Core.csproj b/YaeBlog.Core/YaeBlog.Core.csproj index 844ae04..ef44f03 100644 --- a/YaeBlog.Core/YaeBlog.Core.csproj +++ b/YaeBlog.Core/YaeBlog.Core.csproj @@ -6,20 +6,6 @@ enable - - YaeBlog.Core - 0.1.0 - Ricardo Ren - Ricardo Ren - MIT - README.md - - - - - - - @@ -32,12 +18,6 @@ - - - - - - diff --git a/YaeBlog.sln b/YaeBlog.sln index c74cbfb..78aae25 100644 --- a/YaeBlog.sln +++ b/YaeBlog.sln @@ -14,6 +14,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .gitea\workflows\build.yaml = .gitea\workflows\build.yaml EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{753B998C-1B9E-498F-B949-845CE86C4075}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + README.md = README.md + LICENSE = LICENSE + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/YaeBlog/Commands/Binders/BlogOptionsBinder.cs b/YaeBlog/Commands/Binders/BlogOptionsBinder.cs new file mode 100644 index 0000000..9f3cb54 --- /dev/null +++ b/YaeBlog/Commands/Binders/BlogOptionsBinder.cs @@ -0,0 +1,36 @@ +using System.CommandLine.Binding; +using System.Text.Json; +using Microsoft.Extensions.Options; +using YaeBlog.Core.Models; + +namespace YaeBlog.Commands.Binders; + +public sealed class BlogOptionsBinder : BinderBase> +{ + protected override IOptions GetBoundValue(BindingContext bindingContext) + { + bindingContext.AddService>(_ => + { + 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(); + if (result is null) + { + throw new InvalidOperationException("Failed to load YaeBlog configuration in appsettings.json."); + } + + return new OptionsWrapper(result); + }); + + return bindingContext.GetRequiredService>(); + } +} diff --git a/YaeBlog/Commands/Binders/EssayScanServiceBinder.cs b/YaeBlog/Commands/Binders/EssayScanServiceBinder.cs new file mode 100644 index 0000000..b0613c8 --- /dev/null +++ b/YaeBlog/Commands/Binders/EssayScanServiceBinder.cs @@ -0,0 +1,32 @@ +using System.CommandLine.Binding; +using Microsoft.Extensions.Options; +using YaeBlog.Core.Abstractions; +using YaeBlog.Core.Models; +using YaeBlog.Core.Services; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace YaeBlog.Commands.Binders; + +public sealed class EssayScanServiceBinder : BinderBase +{ + protected override IEssayScanService GetBoundValue(BindingContext bindingContext) + { + bindingContext.AddService(provider => + { + DeserializerBuilder deserializerBuilder = new(); + deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); + deserializerBuilder.IgnoreUnmatchedProperties(); + + SerializerBuilder serializerBuilder = new(); + serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); + + IOptions options = provider.GetRequiredService>(); + ILogger logger = provider.GetRequiredService>(); + + return new EssayScanService(serializerBuilder.Build(), deserializerBuilder.Build(), options, logger); + }); + + return bindingContext.GetRequiredService(); + } +} diff --git a/YaeBlog/Commands/Binders/LoggerBinder.cs b/YaeBlog/Commands/Binders/LoggerBinder.cs new file mode 100644 index 0000000..47bd24c --- /dev/null +++ b/YaeBlog/Commands/Binders/LoggerBinder.cs @@ -0,0 +1,18 @@ +using System.CommandLine.Binding; + +namespace YaeBlog.Commands.Binders; + +public sealed class LoggerBinder : BinderBase> +{ + protected override ILogger GetBoundValue(BindingContext bindingContext) + { + bindingContext.AddService(_ => LoggerFactory.Create(builder => builder.AddConsole())); + bindingContext.AddService>(provider => + { + ILoggerFactory factory = provider.GetRequiredService(); + return factory.CreateLogger(); + }); + + return bindingContext.GetRequiredService>(); + } +} diff --git a/YaeBlog/Commands/BlogOptionsBinder.cs b/YaeBlog/Commands/BlogOptionsBinder.cs deleted file mode 100644 index 2641dbd..0000000 --- a/YaeBlog/Commands/BlogOptionsBinder.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.CommandLine.Binding; -using System.Text.Json; -using YaeBlog.Core.Models; - -namespace YaeBlog.Commands; - -public sealed class BlogOptionsBinder : BinderBase -{ - protected override BlogOptions GetBoundValue(BindingContext bindingContext) - { - 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(); - if (result is null) - { - throw new InvalidOperationException("Failed to load YaeBlog configuration in appsettings.json."); - } - - return result; - } -} diff --git a/YaeBlog/Commands/CommandExtensions.cs b/YaeBlog/Commands/CommandExtensions.cs index b5b0b03..0ea6ac6 100644 --- a/YaeBlog/Commands/CommandExtensions.cs +++ b/YaeBlog/Commands/CommandExtensions.cs @@ -1,12 +1,15 @@ using System.CommandLine; +using YaeBlog.Commands.Binders; using YaeBlog.Components; using YaeBlog.Core.Extensions; +using YaeBlog.Core.Models; +using YaeBlog.Core.Services; namespace YaeBlog.Commands; public static class CommandExtensions { - public static Command AddServeCommand(this RootCommand rootCommand) + public static void AddServeCommand(this RootCommand rootCommand) { Command serveCommand = new("serve", "Start http server."); rootCommand.AddCommand(serveCommand); @@ -20,6 +23,7 @@ public static class CommandExtensions builder.Services.AddControllers(); builder.Services.AddBlazorBootstrap(); builder.AddYaeBlog(); + builder.AddServer(); WebApplication application = builder.Build(); @@ -34,11 +38,40 @@ public static class CommandExtensions CancellationToken token = context.GetCancellationToken(); await application.RunAsync(token); }); - - return rootCommand; } - public static Command AddNewCommand(this RootCommand rootCommand) + public static void AddWatchCommand(this 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.Services.AddBlazorBootstrap(); + builder.AddYaeBlog(); + builder.AddWatcher(); + + WebApplication application = builder.Build(); + + application.UseStaticFiles(); + application.UseAntiforgery(); + application.UseYaeBlog(); + + application.MapRazorComponents() + .AddInteractiveServerRenderMode(); + application.MapControllers(); + + CancellationToken token = context.GetCancellationToken(); + await application.RunAsync(token); + }); + } + + public static void AddNewCommand(this RootCommand rootCommand) { Command newCommand = new("new", "Create a new blog file and image directory."); rootCommand.AddCommand(newCommand); @@ -46,45 +79,40 @@ public static class CommandExtensions Argument filenameArgument = new(name: "blog name", description: "The created blog filename."); newCommand.AddArgument(filenameArgument); - newCommand.SetHandler(async (file, blogOptions) => + newCommand.SetHandler(async (file, _, _, essayScanService) => + { + await essayScanService.SaveBlogContent(new BlogContent + { + FileName = file, + FileContent = string.Empty, + Metadata = new MarkdownMetadata { Title = file, Date = DateTime.Now } + }); + + Console.WriteLine($"Created new blog '{file}."); + }, filenameArgument, new BlogOptionsBinder(), new LoggerBinder(), + new EssayScanServiceBinder()); + } + + public static void AddListCommand(this RootCommand rootCommand) + { + Command command = new("list", "List all blogs"); + rootCommand.Add(command); + + command.SetHandler(async (_, _, essyScanService) => { - string fileWithExtension; - if (file.EndsWith(".md")) + BlogContents contents = await essyScanService.ScanContents(); + + Console.WriteLine($"All {contents.Posts.Count} Posts:"); + foreach (BlogContent content in contents.Posts) { - fileWithExtension = file; - file = fileWithExtension[..fileWithExtension.LastIndexOf('.')]; - } - else - { - fileWithExtension = file + ".md"; + Console.WriteLine($" - {content.FileName}"); } - DirectoryInfo rootDir = new(Path.Combine(Environment.CurrentDirectory, blogOptions.Root)); - if (!rootDir.Exists) + Console.WriteLine($"All {contents.Drafts.Count} Drafts:"); + foreach (BlogContent content in contents.Drafts) { - throw new InvalidOperationException($"Blog source directory '{blogOptions.Root} doesn't exist."); + Console.WriteLine($" - {content.FileName}"); } - - if (rootDir.EnumerateFiles().Any(f => f.Name == fileWithExtension)) - { - throw new InvalidOperationException($"Target blog '{file}' has been created!"); - } - - FileInfo newBlogFile = new(Path.Combine(rootDir.FullName, fileWithExtension)); - await using StreamWriter newStream = newBlogFile.CreateText(); - - await newStream.WriteAsync($""" - --- - title: {file} - tags: - --- - - """); - - Console.WriteLine($"Created new blog '{file}."); - }, filenameArgument, new BlogOptionsBinder()); - - - return newCommand; + }, new BlogOptionsBinder(), new LoggerBinder(), new EssayScanServiceBinder()); } } diff --git a/YaeBlog/Pages/Essays.razor b/YaeBlog/Pages/Essays.razor index c61a9ea..9fdb685 100644 --- a/YaeBlog/Pages/Essays.razor +++ b/YaeBlog/Pages/Essays.razor @@ -4,7 +4,6 @@ @using YaeBlog.Core.Models @inject IEssayContentService Contents -@inject ITableOfContentService TableOfContent @inject NavigationManager NavigationInstance @@ -129,7 +128,7 @@ NavigationInstance.NavigateTo("/NotFound"); } - _headline = TableOfContent.Headlines[BlogKey]; + _headline = Contents.Headlines[BlogKey]; } private string GenerateSelectorUrl(string selectorId) diff --git a/YaeBlog/Program.cs b/YaeBlog/Program.cs index fc5acd2..4cd3540 100644 --- a/YaeBlog/Program.cs +++ b/YaeBlog/Program.cs @@ -5,5 +5,7 @@ RootCommand rootCommand = new("YaeBlog CLI"); rootCommand.AddServeCommand(); rootCommand.AddNewCommand(); +rootCommand.AddListCommand(); +rootCommand.AddWatchCommand(); await rootCommand.InvokeAsync(args); diff --git a/YaeBlog/source/drafts/test-essay.md b/YaeBlog/source/drafts/test-essay.md new file mode 100644 index 0000000..2eb5d88 --- /dev/null +++ b/YaeBlog/source/drafts/test-essay.md @@ -0,0 +1,6 @@ +--- +title: test-essay +date: 2024-08-22T22:31:34.3177253+08:00 +tags: +--- + diff --git a/YaeBlog/source/2021-final.md b/YaeBlog/source/posts/2021-final.md similarity index 100% rename from YaeBlog/source/2021-final.md rename to YaeBlog/source/posts/2021-final.md diff --git a/YaeBlog/source/2021-final/1.png b/YaeBlog/source/posts/2021-final/1.png similarity index 100% rename from YaeBlog/source/2021-final/1.png rename to YaeBlog/source/posts/2021-final/1.png diff --git a/YaeBlog/source/2022-final.md b/YaeBlog/source/posts/2022-final.md similarity index 100% rename from YaeBlog/source/2022-final.md rename to YaeBlog/source/posts/2022-final.md diff --git a/YaeBlog/source/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg b/YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg similarity index 100% rename from YaeBlog/source/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg rename to YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg diff --git a/YaeBlog/source/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg b/YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg similarity index 100% rename from YaeBlog/source/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg rename to YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg diff --git a/YaeBlog/source/2022-summer-vacation.md b/YaeBlog/source/posts/2022-summer-vacation.md similarity index 100% rename from YaeBlog/source/2022-summer-vacation.md rename to YaeBlog/source/posts/2022-summer-vacation.md diff --git a/YaeBlog/source/2022-summer-vacation/result1.png b/YaeBlog/source/posts/2022-summer-vacation/result1.png similarity index 100% rename from YaeBlog/source/2022-summer-vacation/result1.png rename to YaeBlog/source/posts/2022-summer-vacation/result1.png diff --git a/YaeBlog/source/2023-final.md b/YaeBlog/source/posts/2023-final.md similarity index 100% rename from YaeBlog/source/2023-final.md rename to YaeBlog/source/posts/2023-final.md diff --git a/YaeBlog/source/2023-final/image-20240303165826486.png b/YaeBlog/source/posts/2023-final/image-20240303165826486.png similarity index 100% rename from YaeBlog/source/2023-final/image-20240303165826486.png rename to YaeBlog/source/posts/2023-final/image-20240303165826486.png diff --git a/YaeBlog/source/archlinux-sop.md b/YaeBlog/source/posts/archlinux-sop.md similarity index 100% rename from YaeBlog/source/archlinux-sop.md rename to YaeBlog/source/posts/archlinux-sop.md diff --git a/YaeBlog/source/big-homework.md b/YaeBlog/source/posts/big-homework.md similarity index 100% rename from YaeBlog/source/big-homework.md rename to YaeBlog/source/posts/big-homework.md diff --git a/YaeBlog/source/big-homework/1.png b/YaeBlog/source/posts/big-homework/1.png similarity index 100% rename from YaeBlog/source/big-homework/1.png rename to YaeBlog/source/posts/big-homework/1.png diff --git a/YaeBlog/source/build-blog-record.md b/YaeBlog/source/posts/build-blog-record.md similarity index 100% rename from YaeBlog/source/build-blog-record.md rename to YaeBlog/source/posts/build-blog-record.md diff --git a/YaeBlog/source/build-blog-record/1.png b/YaeBlog/source/posts/build-blog-record/1.png similarity index 100% rename from YaeBlog/source/build-blog-record/1.png rename to YaeBlog/source/posts/build-blog-record/1.png diff --git a/YaeBlog/source/build-blog-record/2.png b/YaeBlog/source/posts/build-blog-record/2.png similarity index 100% rename from YaeBlog/source/build-blog-record/2.png rename to YaeBlog/source/posts/build-blog-record/2.png diff --git a/YaeBlog/source/c-include-problems.md b/YaeBlog/source/posts/c-include-problems.md similarity index 100% rename from YaeBlog/source/c-include-problems.md rename to YaeBlog/source/posts/c-include-problems.md diff --git a/YaeBlog/source/c-include-problems/1.png b/YaeBlog/source/posts/c-include-problems/1.png similarity index 100% rename from YaeBlog/source/c-include-problems/1.png rename to YaeBlog/source/posts/c-include-problems/1.png diff --git a/YaeBlog/source/c-include-problems/2.png b/YaeBlog/source/posts/c-include-problems/2.png similarity index 100% rename from YaeBlog/source/c-include-problems/2.png rename to YaeBlog/source/posts/c-include-problems/2.png diff --git a/YaeBlog/source/compile-mediapipe.md b/YaeBlog/source/posts/compile-mediapipe.md similarity index 100% rename from YaeBlog/source/compile-mediapipe.md rename to YaeBlog/source/posts/compile-mediapipe.md diff --git a/YaeBlog/source/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png b/YaeBlog/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png similarity index 100% rename from YaeBlog/source/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png rename to YaeBlog/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png diff --git a/YaeBlog/source/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png b/YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png similarity index 100% rename from YaeBlog/source/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png rename to YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png diff --git a/YaeBlog/source/compile-mediapipe/2023-01-19-20-21-30-Screenshot_20230119_202008.png b/YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-21-30-Screenshot_20230119_202008.png similarity index 100% rename from YaeBlog/source/compile-mediapipe/2023-01-19-20-21-30-Screenshot_20230119_202008.png rename to YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-21-30-Screenshot_20230119_202008.png diff --git a/YaeBlog/source/computer-architecture-pipeline.md b/YaeBlog/source/posts/computer-architecture-pipeline.md similarity index 100% rename from YaeBlog/source/computer-architecture-pipeline.md rename to YaeBlog/source/posts/computer-architecture-pipeline.md diff --git a/YaeBlog/source/computer-architecture-pipeline/image-20240612184855300.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184855300.png similarity index 100% rename from YaeBlog/source/computer-architecture-pipeline/image-20240612184855300.png rename to YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184855300.png diff --git a/YaeBlog/source/computer-architecture-pipeline/image-20240612184949777.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184949777.png similarity index 100% rename from YaeBlog/source/computer-architecture-pipeline/image-20240612184949777.png rename to YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184949777.png diff --git a/YaeBlog/source/computer-architecture-pipeline/image-20240612190426368.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612190426368.png similarity index 100% rename from YaeBlog/source/computer-architecture-pipeline/image-20240612190426368.png rename to YaeBlog/source/posts/computer-architecture-pipeline/image-20240612190426368.png diff --git a/YaeBlog/source/computer-architecture-pipeline/image-20240612192700169.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612192700169.png similarity index 100% rename from YaeBlog/source/computer-architecture-pipeline/image-20240612192700169.png rename to YaeBlog/source/posts/computer-architecture-pipeline/image-20240612192700169.png diff --git a/YaeBlog/source/computer-architecture-pipeline/image-20240612193301372.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612193301372.png similarity index 100% rename from YaeBlog/source/computer-architecture-pipeline/image-20240612193301372.png rename to YaeBlog/source/posts/computer-architecture-pipeline/image-20240612193301372.png diff --git a/YaeBlog/source/daily-linux-0.md b/YaeBlog/source/posts/daily-linux-0.md similarity index 100% rename from YaeBlog/source/daily-linux-0.md rename to YaeBlog/source/posts/daily-linux-0.md diff --git a/YaeBlog/source/daily-linux-0/2023-01-12-13-28-11-Screenshot_20230112_132756.png b/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-11-Screenshot_20230112_132756.png similarity index 100% rename from YaeBlog/source/daily-linux-0/2023-01-12-13-28-11-Screenshot_20230112_132756.png rename to YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-11-Screenshot_20230112_132756.png diff --git a/YaeBlog/source/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.png b/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.png similarity index 100% rename from YaeBlog/source/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.png rename to YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.png diff --git a/YaeBlog/source/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.png b/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.png similarity index 100% rename from YaeBlog/source/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.png rename to YaeBlog/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.png diff --git a/YaeBlog/source/daily-linux-1.md b/YaeBlog/source/posts/daily-linux-1.md similarity index 100% rename from YaeBlog/source/daily-linux-1.md rename to YaeBlog/source/posts/daily-linux-1.md diff --git a/YaeBlog/source/daily-linux-2.md b/YaeBlog/source/posts/daily-linux-2.md similarity index 100% rename from YaeBlog/source/daily-linux-2.md rename to YaeBlog/source/posts/daily-linux-2.md diff --git a/YaeBlog/source/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.jpg b/YaeBlog/source/posts/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.jpg similarity index 100% rename from YaeBlog/source/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.jpg rename to YaeBlog/source/posts/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.jpg diff --git a/YaeBlog/source/daily-linux-2/image-20230702205919301.png b/YaeBlog/source/posts/daily-linux-2/image-20230702205919301.png similarity index 100% rename from YaeBlog/source/daily-linux-2/image-20230702205919301.png rename to YaeBlog/source/posts/daily-linux-2/image-20230702205919301.png diff --git a/YaeBlog/source/daily-linux-2/image-20230720202327220.png b/YaeBlog/source/posts/daily-linux-2/image-20230720202327220.png similarity index 100% rename from YaeBlog/source/daily-linux-2/image-20230720202327220.png rename to YaeBlog/source/posts/daily-linux-2/image-20230720202327220.png diff --git a/YaeBlog/source/daily-linux-3.md b/YaeBlog/source/posts/daily-linux-3.md similarity index 100% rename from YaeBlog/source/daily-linux-3.md rename to YaeBlog/source/posts/daily-linux-3.md diff --git a/YaeBlog/source/daily-linux-3/Screenshot_20230904_144149.png b/YaeBlog/source/posts/daily-linux-3/Screenshot_20230904_144149.png similarity index 100% rename from YaeBlog/source/daily-linux-3/Screenshot_20230904_144149.png rename to YaeBlog/source/posts/daily-linux-3/Screenshot_20230904_144149.png diff --git a/YaeBlog/source/daily-linux-4.md b/YaeBlog/source/posts/daily-linux-4.md similarity index 100% rename from YaeBlog/source/daily-linux-4.md rename to YaeBlog/source/posts/daily-linux-4.md diff --git a/YaeBlog/source/daily-linux-4/Screenshot_20240309_115143.png b/YaeBlog/source/posts/daily-linux-4/Screenshot_20240309_115143.png similarity index 100% rename from YaeBlog/source/daily-linux-4/Screenshot_20240309_115143.png rename to YaeBlog/source/posts/daily-linux-4/Screenshot_20240309_115143.png diff --git a/YaeBlog/source/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg b/YaeBlog/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg similarity index 100% rename from YaeBlog/source/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg rename to YaeBlog/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg diff --git a/YaeBlog/source/daily-linux-4/image-20240309130329784.png b/YaeBlog/source/posts/daily-linux-4/image-20240309130329784.png similarity index 100% rename from YaeBlog/source/daily-linux-4/image-20240309130329784.png rename to YaeBlog/source/posts/daily-linux-4/image-20240309130329784.png diff --git a/YaeBlog/source/daily-linux-4/image-20240309131750535.png b/YaeBlog/source/posts/daily-linux-4/image-20240309131750535.png similarity index 100% rename from YaeBlog/source/daily-linux-4/image-20240309131750535.png rename to YaeBlog/source/posts/daily-linux-4/image-20240309131750535.png diff --git a/YaeBlog/source/daily-linux-4/image-20240309134847166.png b/YaeBlog/source/posts/daily-linux-4/image-20240309134847166.png similarity index 100% rename from YaeBlog/source/daily-linux-4/image-20240309134847166.png rename to YaeBlog/source/posts/daily-linux-4/image-20240309134847166.png diff --git a/YaeBlog/source/environment-setting.md b/YaeBlog/source/posts/environment-setting.md similarity index 100% rename from YaeBlog/source/environment-setting.md rename to YaeBlog/source/posts/environment-setting.md diff --git a/YaeBlog/source/environment-setting/6.png b/YaeBlog/source/posts/environment-setting/6.png similarity index 100% rename from YaeBlog/source/environment-setting/6.png rename to YaeBlog/source/posts/environment-setting/6.png diff --git a/YaeBlog/source/genshin-gacha-1.md b/YaeBlog/source/posts/genshin-gacha-1.md similarity index 100% rename from YaeBlog/source/genshin-gacha-1.md rename to YaeBlog/source/posts/genshin-gacha-1.md diff --git a/YaeBlog/source/genshin-gacha-1/2022-12-31-13-06-36-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.png similarity index 100% rename from YaeBlog/source/genshin-gacha-1/2022-12-31-13-06-36-image.png rename to YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.png diff --git a/YaeBlog/source/genshin-gacha-1/2022-12-31-13-20-46-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.png similarity index 100% rename from YaeBlog/source/genshin-gacha-1/2022-12-31-13-20-46-image.png rename to YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.png diff --git a/YaeBlog/source/genshin-gacha-1/2022-12-31-13-21-11-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.png similarity index 100% rename from YaeBlog/source/genshin-gacha-1/2022-12-31-13-21-11-image.png rename to YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.png diff --git a/YaeBlog/source/genshin-gacha-1/2022-12-31-13-24-26-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.png similarity index 100% rename from YaeBlog/source/genshin-gacha-1/2022-12-31-13-24-26-image.png rename to YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.png diff --git a/YaeBlog/source/genshin-gacha-1/2022-12-31-15-59-20-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.png similarity index 100% rename from YaeBlog/source/genshin-gacha-1/2022-12-31-15-59-20-image.png rename to YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.png diff --git a/YaeBlog/source/genshin-gacha-1/2022-12-31-15-59-42-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.png similarity index 100% rename from YaeBlog/source/genshin-gacha-1/2022-12-31-15-59-42-image.png rename to YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.png diff --git a/YaeBlog/source/genshin-gacha-1/2022-12-31-16-00-10-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.png similarity index 100% rename from YaeBlog/source/genshin-gacha-1/2022-12-31-16-00-10-image.png rename to YaeBlog/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.png diff --git a/YaeBlog/source/install-pytorch.md b/YaeBlog/source/posts/install-pytorch.md similarity index 100% rename from YaeBlog/source/install-pytorch.md rename to YaeBlog/source/posts/install-pytorch.md diff --git a/YaeBlog/source/install-pytorch/1.png b/YaeBlog/source/posts/install-pytorch/1.png similarity index 100% rename from YaeBlog/source/install-pytorch/1.png rename to YaeBlog/source/posts/install-pytorch/1.png diff --git a/YaeBlog/source/install-pytorch/2.png b/YaeBlog/source/posts/install-pytorch/2.png similarity index 100% rename from YaeBlog/source/install-pytorch/2.png rename to YaeBlog/source/posts/install-pytorch/2.png diff --git a/YaeBlog/source/install-pytorch/3.png b/YaeBlog/source/posts/install-pytorch/3.png similarity index 100% rename from YaeBlog/source/install-pytorch/3.png rename to YaeBlog/source/posts/install-pytorch/3.png diff --git a/YaeBlog/source/install-pytorch/4.png b/YaeBlog/source/posts/install-pytorch/4.png similarity index 100% rename from YaeBlog/source/install-pytorch/4.png rename to YaeBlog/source/posts/install-pytorch/4.png diff --git a/YaeBlog/source/laptop-for-computer.md b/YaeBlog/source/posts/laptop-for-computer.md similarity index 100% rename from YaeBlog/source/laptop-for-computer.md rename to YaeBlog/source/posts/laptop-for-computer.md diff --git a/YaeBlog/source/laptop-for-computer/c.png b/YaeBlog/source/posts/laptop-for-computer/c.png similarity index 100% rename from YaeBlog/source/laptop-for-computer/c.png rename to YaeBlog/source/posts/laptop-for-computer/c.png diff --git a/YaeBlog/source/laptop-for-computer/clion.png b/YaeBlog/source/posts/laptop-for-computer/clion.png similarity index 100% rename from YaeBlog/source/laptop-for-computer/clion.png rename to YaeBlog/source/posts/laptop-for-computer/clion.png diff --git a/YaeBlog/source/laptop-for-computer/csharp.png b/YaeBlog/source/posts/laptop-for-computer/csharp.png similarity index 100% rename from YaeBlog/source/laptop-for-computer/csharp.png rename to YaeBlog/source/posts/laptop-for-computer/csharp.png diff --git a/YaeBlog/source/laptop-for-computer/web.png b/YaeBlog/source/posts/laptop-for-computer/web.png similarity index 100% rename from YaeBlog/source/laptop-for-computer/web.png rename to YaeBlog/source/posts/laptop-for-computer/web.png diff --git a/YaeBlog/source/linux-genshin-cloud.md b/YaeBlog/source/posts/linux-genshin-cloud.md similarity index 100% rename from YaeBlog/source/linux-genshin-cloud.md rename to YaeBlog/source/posts/linux-genshin-cloud.md diff --git a/YaeBlog/source/minecraft-wayland.md b/YaeBlog/source/posts/minecraft-wayland.md similarity index 100% rename from YaeBlog/source/minecraft-wayland.md rename to YaeBlog/source/posts/minecraft-wayland.md diff --git a/YaeBlog/source/minecraft-wayland/image-20240105212744116.png b/YaeBlog/source/posts/minecraft-wayland/image-20240105212744116.png similarity index 100% rename from YaeBlog/source/minecraft-wayland/image-20240105212744116.png rename to YaeBlog/source/posts/minecraft-wayland/image-20240105212744116.png diff --git a/YaeBlog/source/minecraft-wayland/image-20240105213439528.png b/YaeBlog/source/posts/minecraft-wayland/image-20240105213439528.png similarity index 100% rename from YaeBlog/source/minecraft-wayland/image-20240105213439528.png rename to YaeBlog/source/posts/minecraft-wayland/image-20240105213439528.png diff --git a/YaeBlog/source/minecraft-wayland/image-20240105213942445.png b/YaeBlog/source/posts/minecraft-wayland/image-20240105213942445.png similarity index 100% rename from YaeBlog/source/minecraft-wayland/image-20240105213942445.png rename to YaeBlog/source/posts/minecraft-wayland/image-20240105213942445.png diff --git a/YaeBlog/source/parser-combinator-performance.md b/YaeBlog/source/posts/parser-combinator-performance.md similarity index 100% rename from YaeBlog/source/parser-combinator-performance.md rename to YaeBlog/source/posts/parser-combinator-performance.md diff --git a/YaeBlog/source/parser-combinator-performance/image-20240819140523087.png b/YaeBlog/source/posts/parser-combinator-performance/image-20240819140523087.png similarity index 100% rename from YaeBlog/source/parser-combinator-performance/image-20240819140523087.png rename to YaeBlog/source/posts/parser-combinator-performance/image-20240819140523087.png diff --git a/YaeBlog/source/parser-combinator.md b/YaeBlog/source/posts/parser-combinator.md similarity index 100% rename from YaeBlog/source/parser-combinator.md rename to YaeBlog/source/posts/parser-combinator.md diff --git a/YaeBlog/source/parser-combinator/image-20240813214315576.png b/YaeBlog/source/posts/parser-combinator/image-20240813214315576.png similarity index 100% rename from YaeBlog/source/parser-combinator/image-20240813214315576.png rename to YaeBlog/source/posts/parser-combinator/image-20240813214315576.png diff --git a/YaeBlog/source/parser-combinator/image-20240813220521028.png b/YaeBlog/source/posts/parser-combinator/image-20240813220521028.png similarity index 100% rename from YaeBlog/source/parser-combinator/image-20240813220521028.png rename to YaeBlog/source/posts/parser-combinator/image-20240813220521028.png diff --git a/YaeBlog/source/parser-combinator/image-20240813220530717.png b/YaeBlog/source/posts/parser-combinator/image-20240813220530717.png similarity index 100% rename from YaeBlog/source/parser-combinator/image-20240813220530717.png rename to YaeBlog/source/posts/parser-combinator/image-20240813220530717.png diff --git a/YaeBlog/source/program-design-introduction.md b/YaeBlog/source/posts/program-design-introduction.md similarity index 100% rename from YaeBlog/source/program-design-introduction.md rename to YaeBlog/source/posts/program-design-introduction.md diff --git a/YaeBlog/source/program-design-introduction/1.png b/YaeBlog/source/posts/program-design-introduction/1.png similarity index 100% rename from YaeBlog/source/program-design-introduction/1.png rename to YaeBlog/source/posts/program-design-introduction/1.png diff --git a/YaeBlog/source/program-design-introduction/2.png b/YaeBlog/source/posts/program-design-introduction/2.png similarity index 100% rename from YaeBlog/source/program-design-introduction/2.png rename to YaeBlog/source/posts/program-design-introduction/2.png diff --git a/YaeBlog/source/program-design-introduction/3.png b/YaeBlog/source/posts/program-design-introduction/3.png similarity index 100% rename from YaeBlog/source/program-design-introduction/3.png rename to YaeBlog/source/posts/program-design-introduction/3.png diff --git a/YaeBlog/source/qt-learning.md b/YaeBlog/source/posts/qt-learning.md similarity index 100% rename from YaeBlog/source/qt-learning.md rename to YaeBlog/source/posts/qt-learning.md diff --git a/YaeBlog/source/qt-learning/1.png b/YaeBlog/source/posts/qt-learning/1.png similarity index 100% rename from YaeBlog/source/qt-learning/1.png rename to YaeBlog/source/posts/qt-learning/1.png diff --git a/YaeBlog/source/question-in-install-vs-2019.md b/YaeBlog/source/posts/question-in-install-vs-2019.md similarity index 100% rename from YaeBlog/source/question-in-install-vs-2019.md rename to YaeBlog/source/posts/question-in-install-vs-2019.md diff --git a/YaeBlog/source/question-in-install-vs-2019/1.png b/YaeBlog/source/posts/question-in-install-vs-2019/1.png similarity index 100% rename from YaeBlog/source/question-in-install-vs-2019/1.png rename to YaeBlog/source/posts/question-in-install-vs-2019/1.png diff --git a/YaeBlog/source/software-engineer.md b/YaeBlog/source/posts/software-engineer.md similarity index 100% rename from YaeBlog/source/software-engineer.md rename to YaeBlog/source/posts/software-engineer.md diff --git a/YaeBlog/source/software-engineer/image-20240620211321957.png b/YaeBlog/source/posts/software-engineer/image-20240620211321957.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620211321957.png rename to YaeBlog/source/posts/software-engineer/image-20240620211321957.png diff --git a/YaeBlog/source/software-engineer/image-20240620212101864.png b/YaeBlog/source/posts/software-engineer/image-20240620212101864.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620212101864.png rename to YaeBlog/source/posts/software-engineer/image-20240620212101864.png diff --git a/YaeBlog/source/software-engineer/image-20240620214307906.png b/YaeBlog/source/posts/software-engineer/image-20240620214307906.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620214307906.png rename to YaeBlog/source/posts/software-engineer/image-20240620214307906.png diff --git a/YaeBlog/source/software-engineer/image-20240620214739548.png b/YaeBlog/source/posts/software-engineer/image-20240620214739548.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620214739548.png rename to YaeBlog/source/posts/software-engineer/image-20240620214739548.png diff --git a/YaeBlog/source/software-engineer/image-20240620215022645.png b/YaeBlog/source/posts/software-engineer/image-20240620215022645.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620215022645.png rename to YaeBlog/source/posts/software-engineer/image-20240620215022645.png diff --git a/YaeBlog/source/software-engineer/image-20240620220155962.png b/YaeBlog/source/posts/software-engineer/image-20240620220155962.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620220155962.png rename to YaeBlog/source/posts/software-engineer/image-20240620220155962.png diff --git a/YaeBlog/source/software-engineer/image-20240620224318982.png b/YaeBlog/source/posts/software-engineer/image-20240620224318982.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620224318982.png rename to YaeBlog/source/posts/software-engineer/image-20240620224318982.png diff --git a/YaeBlog/source/software-engineer/image-20240620224519243.png b/YaeBlog/source/posts/software-engineer/image-20240620224519243.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240620224519243.png rename to YaeBlog/source/posts/software-engineer/image-20240620224519243.png diff --git a/YaeBlog/source/software-engineer/image-20240621114540033.png b/YaeBlog/source/posts/software-engineer/image-20240621114540033.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240621114540033.png rename to YaeBlog/source/posts/software-engineer/image-20240621114540033.png diff --git a/YaeBlog/source/software-engineer/image-20240623160826903.png b/YaeBlog/source/posts/software-engineer/image-20240623160826903.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240623160826903.png rename to YaeBlog/source/posts/software-engineer/image-20240623160826903.png diff --git a/YaeBlog/source/software-engineer/image-20240623162229404.png b/YaeBlog/source/posts/software-engineer/image-20240623162229404.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240623162229404.png rename to YaeBlog/source/posts/software-engineer/image-20240623162229404.png diff --git a/YaeBlog/source/software-engineer/image-20240623163647935.png b/YaeBlog/source/posts/software-engineer/image-20240623163647935.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240623163647935.png rename to YaeBlog/source/posts/software-engineer/image-20240623163647935.png diff --git a/YaeBlog/source/software-engineer/image-20240623170058679.png b/YaeBlog/source/posts/software-engineer/image-20240623170058679.png similarity index 100% rename from YaeBlog/source/software-engineer/image-20240623170058679.png rename to YaeBlog/source/posts/software-engineer/image-20240623170058679.png diff --git a/YaeBlog/source/spring-boot-custom-authorize.md b/YaeBlog/source/posts/spring-boot-custom-authorize.md similarity index 100% rename from YaeBlog/source/spring-boot-custom-authorize.md rename to YaeBlog/source/posts/spring-boot-custom-authorize.md diff --git a/YaeBlog/source/spring-boot-custom-authorize/image-20230727175807814.png b/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175807814.png similarity index 100% rename from YaeBlog/source/spring-boot-custom-authorize/image-20230727175807814.png rename to YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175807814.png diff --git a/YaeBlog/source/spring-boot-custom-authorize/image-20230727175955817.png b/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175955817.png similarity index 100% rename from YaeBlog/source/spring-boot-custom-authorize/image-20230727175955817.png rename to YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175955817.png diff --git a/YaeBlog/source/update-windows-break-archlinux.md b/YaeBlog/source/posts/update-windows-break-archlinux.md similarity index 100% rename from YaeBlog/source/update-windows-break-archlinux.md rename to YaeBlog/source/posts/update-windows-break-archlinux.md diff --git a/YaeBlog/source/using-vpn-elegant.md b/YaeBlog/source/posts/using-vpn-elegant.md similarity index 100% rename from YaeBlog/source/using-vpn-elegant.md rename to YaeBlog/source/posts/using-vpn-elegant.md diff --git a/YaeBlog/source/vscode-in-browser.md b/YaeBlog/source/posts/vscode-in-browser.md similarity index 100% rename from YaeBlog/source/vscode-in-browser.md rename to YaeBlog/source/posts/vscode-in-browser.md diff --git a/YaeBlog/source/vscode-in-browser/1.png b/YaeBlog/source/posts/vscode-in-browser/1.png similarity index 100% rename from YaeBlog/source/vscode-in-browser/1.png rename to YaeBlog/source/posts/vscode-in-browser/1.png diff --git a/YaeBlog/source/vscode-in-browser/2.png b/YaeBlog/source/posts/vscode-in-browser/2.png similarity index 100% rename from YaeBlog/source/vscode-in-browser/2.png rename to YaeBlog/source/posts/vscode-in-browser/2.png diff --git a/YaeBlog/source/whats-new-of-dotnet-8.md b/YaeBlog/source/posts/whats-new-of-dotnet-8.md similarity index 100% rename from YaeBlog/source/whats-new-of-dotnet-8.md rename to YaeBlog/source/posts/whats-new-of-dotnet-8.md diff --git a/YaeBlog/source/whats-new-of-dotnet-8/AOTOptimizations4.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.png similarity index 100% rename from YaeBlog/source/whats-new-of-dotnet-8/AOTOptimizations4.png rename to YaeBlog/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.png diff --git a/YaeBlog/source/whats-new-of-dotnet-8/image-20231122100930849.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122100930849.png similarity index 100% rename from YaeBlog/source/whats-new-of-dotnet-8/image-20231122100930849.png rename to YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122100930849.png diff --git a/YaeBlog/source/whats-new-of-dotnet-8/image-20231122101012416.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101012416.png similarity index 100% rename from YaeBlog/source/whats-new-of-dotnet-8/image-20231122101012416.png rename to YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101012416.png diff --git a/YaeBlog/source/whats-new-of-dotnet-8/image-20231122101142080.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101142080.png similarity index 100% rename from YaeBlog/source/whats-new-of-dotnet-8/image-20231122101142080.png rename to YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101142080.png diff --git a/YaeBlog/source/wsl-setup-csapp.md b/YaeBlog/source/posts/wsl-setup-csapp.md similarity index 100% rename from YaeBlog/source/wsl-setup-csapp.md rename to YaeBlog/source/posts/wsl-setup-csapp.md diff --git a/YaeBlog/source/wsl-setup-csapp/1.png b/YaeBlog/source/posts/wsl-setup-csapp/1.png similarity index 100% rename from YaeBlog/source/wsl-setup-csapp/1.png rename to YaeBlog/source/posts/wsl-setup-csapp/1.png diff --git a/YaeBlog/source/wsl-setup-csapp/2.png b/YaeBlog/source/posts/wsl-setup-csapp/2.png similarity index 100% rename from YaeBlog/source/wsl-setup-csapp/2.png rename to YaeBlog/source/posts/wsl-setup-csapp/2.png diff --git a/YaeBlog/source/wsl-setup-csapp/3.png b/YaeBlog/source/posts/wsl-setup-csapp/3.png similarity index 100% rename from YaeBlog/source/wsl-setup-csapp/3.png rename to YaeBlog/source/posts/wsl-setup-csapp/3.png