feat: 美化文章界面 (#3)
All checks were successful
Build blog docker image / Build-Blog-Image (push) Successful in 3m57s
All checks were successful
Build blog docker image / Build-Blog-Image (push) Successful in 3m57s
Reviewed-on: #3
This commit is contained in:
@@ -1,57 +1,48 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using YaeBlog.Core.Abstractions;
|
||||
using YaeBlog.Core.Models;
|
||||
|
||||
namespace YaeBlog.Core.Services;
|
||||
|
||||
public class EssayContentService
|
||||
public class EssayContentService : IEssayContentService
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, BlogEssay> _essays = new();
|
||||
|
||||
private readonly Dictionary<string, List<BlogEssay>> _tags = [];
|
||||
private readonly Dictionary<EssayTag, List<BlogEssay>> _tags = [];
|
||||
|
||||
public bool TryGet(string key, out BlogEssay? essay)
|
||||
=> _essays.TryGetValue(key, out essay);
|
||||
public bool TryAdd(BlogEssay essay) => _essays.TryAdd(essay.FileName, essay);
|
||||
|
||||
public bool TryAdd(string key, BlogEssay essay) => _essays.TryAdd(key, essay);
|
||||
public IReadOnlyDictionary<string, BlogEssay> Essays => _essays;
|
||||
|
||||
public IDictionary<string, BlogEssay> Essays => _essays;
|
||||
public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags => _tags;
|
||||
|
||||
public void RefreshTags()
|
||||
{
|
||||
foreach (BlogEssay essay in _essays.Values)
|
||||
{
|
||||
foreach (string tag in essay.Tags)
|
||||
{
|
||||
if (_tags.TryGetValue(tag, out var list))
|
||||
{
|
||||
list.Add(essay);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tags[tag] = [essay];
|
||||
}
|
||||
}
|
||||
}
|
||||
_tags.Clear();
|
||||
|
||||
foreach (KeyValuePair<string,List<BlogEssay>> pair in _tags)
|
||||
{
|
||||
pair.Value.Sort();
|
||||
}
|
||||
foreach (BlogEssay essay in _essays.Values)
|
||||
{
|
||||
foreach (EssayTag essayTag in essay.Tags.Select(tag => new EssayTag(tag)))
|
||||
{
|
||||
if (_tags.TryGetValue(essayTag, out List<BlogEssay>? essays))
|
||||
{
|
||||
essays.Add(essay);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tags.Add(essayTag, [essay]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<string, int>> Tags => from item in _tags
|
||||
orderby item.Value.Count descending
|
||||
select KeyValuePair.Create(item.Key, item.Value.Count);
|
||||
|
||||
public int TagCount => _tags.Count;
|
||||
|
||||
public IEnumerable<BlogEssay> GetTag(string tag)
|
||||
public bool SearchByUrlEncodedTag(string tag, [NotNullWhen(true)] out List<BlogEssay>? result)
|
||||
{
|
||||
if (_tags.TryGetValue(tag, out var list))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
result = (from item in _tags
|
||||
where item.Key.UrlEncodedTagName == tag
|
||||
select item.Value).FirstOrDefault();
|
||||
|
||||
throw new KeyNotFoundException("Selected tag not found.");
|
||||
return result is not null;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Markdig;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using YaeBlog.Core.Abstractions;
|
||||
@@ -11,7 +12,8 @@ using YamlDotNet.Serialization;
|
||||
|
||||
namespace YaeBlog.Core.Services;
|
||||
|
||||
public class RendererService(ILogger<RendererService> logger,
|
||||
public partial class RendererService(
|
||||
ILogger<RendererService> logger,
|
||||
EssayScanService essayScanService,
|
||||
MarkdownPipeline markdownPipeline,
|
||||
IDeserializer yamlDeserializer,
|
||||
@@ -37,12 +39,14 @@ public class RendererService(ILogger<RendererService> logger,
|
||||
foreach (BlogContent content in preProcessedContents)
|
||||
{
|
||||
MarkdownMetadata? metadata = TryParseMetadata(content);
|
||||
uint wordCount = GetWordCount(content);
|
||||
BlogEssay essay = new()
|
||||
{
|
||||
Title = metadata?.Title ?? content.FileName,
|
||||
FileName = content.FileName,
|
||||
Description = GetDescription(content),
|
||||
WordCount = GetWordCount(content),
|
||||
WordCount = wordCount,
|
||||
ReadTime = CalculateReadTime(wordCount),
|
||||
PublishTime = metadata?.Date ?? DateTime.Now,
|
||||
HtmlContent = content.FileContent
|
||||
};
|
||||
@@ -51,6 +55,7 @@ public class RendererService(ILogger<RendererService> logger,
|
||||
{
|
||||
essay.Tags.AddRange(metadata.Tags);
|
||||
}
|
||||
|
||||
essays.Add(essay);
|
||||
}
|
||||
});
|
||||
@@ -125,7 +130,7 @@ public class RendererService(ILogger<RendererService> logger,
|
||||
essay = await processor.ProcessAsync(essay);
|
||||
}
|
||||
|
||||
if (!essayContentService.TryAdd(essay.FileName, essay))
|
||||
if (!essayContentService.TryAdd(essay))
|
||||
{
|
||||
throw new BlogFileException(
|
||||
$"There are two essays with the same name: '{essay.FileName}'.");
|
||||
@@ -172,30 +177,34 @@ public class RendererService(ILogger<RendererService> logger,
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(?<!\\)[^\#\*_\-\+\`{}\[\]!~]+")]
|
||||
private static partial Regex DescriptionPattern();
|
||||
|
||||
private string GetDescription(BlogContent content)
|
||||
{
|
||||
const string delimiter = "<!--more-->";
|
||||
int pos = content.FileContent.IndexOf(delimiter, StringComparison.Ordinal);
|
||||
StringBuilder builder = new();
|
||||
bool breakSentence = false;
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
// 自动截取前50个字符
|
||||
pos = content.FileContent.Length < 50 ? content.FileContent.Length : 50;
|
||||
breakSentence = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pos; i++)
|
||||
string rawContent = content.FileContent[..pos];
|
||||
MatchCollection matches = DescriptionPattern().Matches(rawContent);
|
||||
|
||||
StringBuilder builder = new();
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
char c = content.FileContent[i];
|
||||
builder.Append(match.Value);
|
||||
}
|
||||
|
||||
if (char.IsControl(c) || char.IsSymbol(c) ||
|
||||
char.IsSeparator(c) || char.IsPunctuation(c) ||
|
||||
char.IsAsciiLetter(c))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(c);
|
||||
if (breakSentence)
|
||||
{
|
||||
builder.Append("……");
|
||||
}
|
||||
|
||||
string description = builder.ToString();
|
||||
@@ -212,7 +221,7 @@ public class RendererService(ILogger<RendererService> logger,
|
||||
foreach (char c in content.FileContent)
|
||||
{
|
||||
if (char.IsControl(c) || char.IsSymbol(c)
|
||||
|| char.IsSeparator(c))
|
||||
|| char.IsSeparator(c))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -224,4 +233,13 @@ public class RendererService(ILogger<RendererService> logger,
|
||||
count);
|
||||
return count;
|
||||
}
|
||||
|
||||
private static string CalculateReadTime(uint wordCount)
|
||||
{
|
||||
// 据说语文教学大纲规定,中国高中问阅读现代文的速度是600字每分钟
|
||||
int second = (int)wordCount / 10;
|
||||
TimeSpan span = new TimeSpan(0, 0, second);
|
||||
|
||||
return span.ToString("mm'分 'ss'秒'");
|
||||
}
|
||||
}
|
||||
|
20
YaeBlog.Core/Services/TableOfContentService.cs
Normal file
20
YaeBlog.Core/Services/TableOfContentService.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Concurrent;
|
||||
using YaeBlog.Core.Abstractions;
|
||||
using YaeBlog.Core.Models;
|
||||
|
||||
namespace YaeBlog.Core.Services;
|
||||
|
||||
public class TableOfContentService : ITableOfContentService
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, BlogHeadline> _headlines = [];
|
||||
|
||||
public IReadOnlyDictionary<string, BlogHeadline> Headlines => _headlines;
|
||||
|
||||
public void AddHeadline(string filename, BlogHeadline headline)
|
||||
{
|
||||
if (!_headlines.TryAdd(filename, headline))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user