Merge branch 'master' into feat-highlight
# Conflicts: # YaeBlog.Core/Processors/CodeBlockPostRenderProcessor.cs # YaeBlog.Core/YaeBlog.Core.csproj
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using YaeBlog.Core.Abstractions;
|
||||
using YaeBlog.Core.Models;
|
||||
|
||||
namespace YaeBlog.Core.Processors;
|
||||
|
||||
public class CodeBlockPostRenderProcessor(ILogger<CodeBlockPostRenderProcessor> logger) : IPostRenderProcessor
|
||||
{
|
||||
public async Task<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||
{
|
||||
BrowsingContext context = new(Configuration.Default);
|
||||
IDocument document = await context.OpenAsync(
|
||||
req => req.Content(essay.HtmlContent));
|
||||
|
||||
IEnumerable<IElement> preElements = from e in document.All
|
||||
where e.LocalName == "pre"
|
||||
select e;
|
||||
|
||||
foreach (IElement element in preElements)
|
||||
{
|
||||
element.ClassList.Add("p-3 text-bg-secondary rounded-1");
|
||||
|
||||
IEnumerable<IElement> codeElements = from e in element.Children
|
||||
where e.LocalName == "code"
|
||||
select e;
|
||||
|
||||
foreach (IElement code in codeElements)
|
||||
{
|
||||
string? language = (from c in code.ClassList
|
||||
where c.StartsWith("language-")
|
||||
select c[9..].ToLower()).FirstOrDefault();
|
||||
|
||||
if (language is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogDebug("Detect code block of language {}.", language);
|
||||
code.InnerHtml = HighLightCode(code.InnerHtml, language);
|
||||
}
|
||||
}
|
||||
|
||||
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
public string Name => nameof(CodeBlockPostRenderProcessor);
|
||||
|
||||
private static string HighLightCode(string code, string language)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
@@ -1,109 +0,0 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using YaeBlog.Core.Abstractions;
|
||||
using YaeBlog.Core.Models;
|
||||
|
||||
namespace YaeBlog.Core.Processors;
|
||||
|
||||
public class HeadlinePostRenderProcessor(
|
||||
IConfiguration angleConfiguration,
|
||||
IEssayContentService essayContentService,
|
||||
ILogger<HeadlinePostRenderProcessor> logger) : IPostRenderProcessor
|
||||
{
|
||||
public async Task<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||
{
|
||||
BrowsingContext browsingContext = new(angleConfiguration);
|
||||
IDocument document = await browsingContext.OpenAsync(req => req.Content(essay.HtmlContent));
|
||||
|
||||
IEnumerable<IElement> elements = from item in document.All
|
||||
where item.LocalName is "h2" or "h3" or "h4"
|
||||
select item;
|
||||
|
||||
BlogHeadline topHeadline = new(essay.Title, "#title");
|
||||
List<BlogHeadline> level2List = [];
|
||||
List<BlogHeadline> level3List = [];
|
||||
List<BlogHeadline> level4List = [];
|
||||
|
||||
foreach (IElement element in elements)
|
||||
{
|
||||
switch (element.LocalName)
|
||||
{
|
||||
case "h2":
|
||||
{
|
||||
FindParentHeadline(topHeadline, level2List, level3List).Children.AddRange(level4List);
|
||||
level4List.Clear();
|
||||
FindParentHeadline(topHeadline, level2List).Children.AddRange(level3List);
|
||||
level3List.Clear();
|
||||
|
||||
BlogHeadline headline = ParserHeadlineElement(element);
|
||||
level2List.Add(headline);
|
||||
break;
|
||||
}
|
||||
case "h3":
|
||||
{
|
||||
FindParentHeadline(topHeadline, level2List, level3List).Children.AddRange(level4List);
|
||||
level4List.Clear();
|
||||
|
||||
BlogHeadline headline = ParserHeadlineElement(element);
|
||||
level3List.Add(headline);
|
||||
break;
|
||||
}
|
||||
case "h4":
|
||||
{
|
||||
BlogHeadline headline = ParserHeadlineElement(element);
|
||||
level4List.Add(headline);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 太抽象了(((
|
||||
FindParentHeadline(topHeadline, level2List, level3List).Children.AddRange(level4List);
|
||||
FindParentHeadline(topHeadline, level2List).Children.AddRange(level3List);
|
||||
topHeadline.Children.AddRange(level2List);
|
||||
|
||||
if (!essayContentService.TryAddHeadline(essay.FileName, topHeadline))
|
||||
{
|
||||
logger.LogWarning("Failed to add headline of {}.", essay.FileName);
|
||||
}
|
||||
|
||||
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
private static BlogHeadline ParserHeadlineElement(IElement element)
|
||||
{
|
||||
element.Id ??= element.TextContent;
|
||||
return new BlogHeadline(element.TextContent, element.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 找到h4标题的父级标题
|
||||
/// </summary>
|
||||
/// <param name="topHeadline"></param>
|
||||
/// <param name="level2"></param>
|
||||
/// <param name="level3"></param>
|
||||
/// <returns></returns>
|
||||
private static BlogHeadline FindParentHeadline(BlogHeadline topHeadline, List<BlogHeadline> level2,
|
||||
List<BlogHeadline> level3)
|
||||
{
|
||||
BlogHeadline? result = level3.LastOrDefault();
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return level2.LastOrDefault() ?? topHeadline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 找到h3标题的父级标题
|
||||
/// </summary>
|
||||
/// <param name="topHeadline"></param>
|
||||
/// <param name="level2"></param>
|
||||
/// <returns></returns>
|
||||
private static BlogHeadline FindParentHeadline(BlogHeadline topHeadline, List<BlogHeadline> level2) =>
|
||||
FindParentHeadline(topHeadline, level2, []);
|
||||
|
||||
public string Name => nameof(HeadlinePostRenderProcessor);
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
using AngleSharp;
|
||||
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;
|
||||
|
||||
public class ImagePostRenderProcessor(ILogger<ImagePostRenderProcessor> logger,
|
||||
IOptions<BlogOptions> options)
|
||||
: IPostRenderProcessor
|
||||
{
|
||||
private static readonly IConfiguration s_configuration = Configuration.Default;
|
||||
|
||||
private readonly BlogOptions _options = options.Value;
|
||||
|
||||
public async Task<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||
{
|
||||
BrowsingContext context = new(s_configuration);
|
||||
IDocument html = await context.OpenAsync(
|
||||
req => req.Content(essay.HtmlContent));
|
||||
|
||||
IEnumerable<IElement> imageElements = from node in html.All
|
||||
where node.LocalName == "img"
|
||||
select node;
|
||||
|
||||
foreach (IElement element in imageElements)
|
||||
{
|
||||
IAttr? attr = element.Attributes.GetNamedItem("src");
|
||||
if (attr is not null)
|
||||
{
|
||||
logger.LogDebug("Found image link: '{}'", attr.Value);
|
||||
attr.Value = GenerateImageLink(attr.Value, essay.FileName);
|
||||
}
|
||||
element.ClassList.Add("essay-image");
|
||||
}
|
||||
return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
public string Name => nameof(ImagePostRenderProcessor);
|
||||
|
||||
private string GenerateImageLink(string filename, string essayFilename)
|
||||
{
|
||||
if (!filename.Contains(essayFilename))
|
||||
{
|
||||
filename = Path.Combine(essayFilename, filename);
|
||||
}
|
||||
|
||||
filename = Path.Combine(_options.Root, "posts", filename);
|
||||
|
||||
if (!Path.Exists(filename))
|
||||
{
|
||||
logger.LogError("Failed to found image: {}.", filename);
|
||||
throw new BlogFileException($"Image {filename} doesn't exist.");
|
||||
}
|
||||
|
||||
string imageLink = "api/files/" + filename;
|
||||
logger.LogDebug("Generate image link '{}' for image file '{}'.",
|
||||
imageLink, filename);
|
||||
|
||||
return imageLink;
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Dom;
|
||||
using YaeBlog.Core.Abstractions;
|
||||
using YaeBlog.Core.Models;
|
||||
|
||||
namespace YaeBlog.Core.Processors;
|
||||
|
||||
public class TablePostRenderProcessor: IPostRenderProcessor
|
||||
{
|
||||
public async Task<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||
{
|
||||
BrowsingContext browsingContext = new(Configuration.Default);
|
||||
IDocument document = await browsingContext.OpenAsync(
|
||||
req => req.Content(essay.HtmlContent));
|
||||
|
||||
IEnumerable<IHtmlTableElement> tableElements = from item in document.All
|
||||
where item.LocalName == "table"
|
||||
select item as IHtmlTableElement;
|
||||
|
||||
foreach (IHtmlTableElement element in tableElements)
|
||||
{
|
||||
IHtmlDivElement divElement = document.CreateElement<IHtmlDivElement>();
|
||||
divElement.InnerHtml = element.OuterHtml;
|
||||
divElement.ClassList.Add("py-2", "table-wrapper");
|
||||
|
||||
element.Replace(divElement);
|
||||
}
|
||||
|
||||
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
public string Name => nameof(TablePostRenderProcessor);
|
||||
}
|
Reference in New Issue
Block a user