feat: 美化文章界面 (#3)
All checks were successful
Build blog docker image / Build-Blog-Image (push) Successful in 3m57s

Reviewed-on: #3
This commit is contained in:
jackfiled 2024-07-29 22:32:26 +08:00
parent ca4f6449d3
commit a483ddc671
62 changed files with 771 additions and 388 deletions

View File

@ -0,0 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using YaeBlog.Core.Models;
namespace YaeBlog.Core.Abstractions;
public interface IEssayContentService
{
public IReadOnlyDictionary<string, BlogEssay> Essays { get; }
public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags { get; }
public bool SearchByUrlEncodedTag(string tag,[NotNullWhen(true)] out List<BlogEssay>? result);
}

View File

@ -0,0 +1,8 @@
using YaeBlog.Core.Models;
namespace YaeBlog.Core.Abstractions;
public interface ITableOfContentService
{
public IReadOnlyDictionary<string, BlogHeadline> Headlines { get; }
}

View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Builder;
using AngleSharp;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using YaeBlog.Core.Abstractions;
using YaeBlog.Core.Models;
using YaeBlog.Core.Processors;
using YaeBlog.Core.Services;
@ -17,10 +19,19 @@ public static class WebApplicationBuilderExtensions
builder.Services.AddMarkdig();
builder.Services.AddYamlParser();
builder.Services.AddSingleton<IConfiguration>(_ => Configuration.Default);
builder.Services.AddSingleton<EssayScanService>();
builder.Services.AddSingleton<RendererService>();
builder.Services.AddSingleton<EssayContentService>();
builder.Services.AddSingleton<IEssayContentService, EssayContentService>(provider =>
provider.GetRequiredService<EssayContentService>());
builder.Services.AddSingleton<TableOfContentService>();
builder.Services.AddSingleton<ITableOfContentService, TableOfContentService>(provider =>
provider.GetRequiredService<TableOfContentService>());
builder.Services.AddTransient<ImagePostRenderProcessor>();
builder.Services.AddTransient<CodeBlockPostRenderProcessor>();
builder.Services.AddTransient<TablePostRenderProcessor>();
builder.Services.AddTransient<HeadlinePostRenderProcessor>();
builder.Services.AddTransient<BlogOptions>(provider =>
provider.GetRequiredService<IOptions<BlogOptions>>().Value);

View File

@ -8,11 +8,12 @@ namespace YaeBlog.Core.Extensions;
public static class WebApplicationExtensions
{
public static WebApplication UseMiddleRenderProcessors(this WebApplication application)
public static void UseYaeBlog(this WebApplication application)
{
application.UsePostRenderProcessor<ImagePostRenderProcessor>();
return application;
application.UsePostRenderProcessor<CodeBlockPostRenderProcessor>();
application.UsePostRenderProcessor<TablePostRenderProcessor>();
application.UsePostRenderProcessor<HeadlinePostRenderProcessor>();
}
private static void UsePreRenderProcessor<T>(this WebApplication application) where T : IPreRenderProcessor

View File

@ -12,6 +12,8 @@ public class BlogEssay : IComparable<BlogEssay>
public required uint WordCount { get; init; }
public required string ReadTime { get; init; }
public List<string> Tags { get; } = [];
public required string HtmlContent { get; init; }
@ -25,6 +27,7 @@ public class BlogEssay : IComparable<BlogEssay>
PublishTime = PublishTime,
Description = Description,
WordCount = WordCount,
ReadTime = ReadTime,
HtmlContent = newHtmlContent
};
essay.Tags.AddRange(Tags);

View File

@ -0,0 +1,10 @@
namespace YaeBlog.Core.Models;
public class BlogHeadline(string title, string selectorId)
{
public string Title { get; } = title;
public string SelectorId { get; set; } = selectorId;
public List<BlogHeadline> Children { get; } = [];
}

View File

@ -10,10 +10,8 @@ public class BlogOptions
public required string Root { get; set; }
/// <summary>
/// 博客作者
/// 博客正文的广而告之
/// </summary>
public required string Author { get; set; }
public required string Announcement { get; set; }
/// <summary>
@ -22,21 +20,7 @@ public class BlogOptions
public required int StartYear { get; set; }
/// <summary>
/// 博客起始页面的背景图片
/// 博客的友链
/// </summary>
public required string BannerImage { get; set; }
/// <summary>
/// 文章页面的背景图片
/// </summary>
public required string EssayImage { get; set; }
/// <summary>
/// 博客底部是否显示ICP备案信息
/// </summary>
public string? RegisterInformation { get; set; }
public required AboutInfo About { get; set; }
public required List<FriendLink> Links { get; set; }
}

View File

@ -0,0 +1,16 @@
using System.Text.Encodings.Web;
namespace YaeBlog.Core.Models;
public class EssayTag(string tagName) : IEquatable<EssayTag>
{
public string TagName { get; } = tagName;
public string UrlEncodedTagName { get; } = UrlEncoder.Default.Encode(tagName);
public bool Equals(EssayTag? other) => other is not null && TagName == other.TagName;
public override bool Equals(object? obj) => obj is EssayTag other && Equals(other);
public override int GetHashCode() => TagName.GetHashCode();
}

View File

@ -0,0 +1,29 @@
using AngleSharp;
using AngleSharp.Dom;
using YaeBlog.Core.Abstractions;
using YaeBlog.Core.Models;
namespace YaeBlog.Core.Processors;
public class CodeBlockPostRenderProcessor : 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");
}
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
}
public string Name => nameof(CodeBlockPostRenderProcessor);
}

View File

@ -0,0 +1,105 @@
using AngleSharp;
using AngleSharp.Dom;
using YaeBlog.Core.Abstractions;
using YaeBlog.Core.Models;
using YaeBlog.Core.Services;
namespace YaeBlog.Core.Processors;
public class HeadlinePostRenderProcessor(
IConfiguration angleConfiguration,
TableOfContentService tableOfContentService) : 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);
tableOfContentService.AddHeadline(essay.FileName, topHeadline);
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);
}

View File

@ -38,7 +38,7 @@ public class ImagePostRenderProcessor(ILogger<ImagePostRenderProcessor> logger,
return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
}
public string Name => "ImagePostRenderProcessor";
public string Name => nameof(ImagePostRenderProcessor);
private string GenerateImageLink(string filename, string essayFilename)
{

View File

@ -0,0 +1,34 @@
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);
}

View File

@ -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()
{
_tags.Clear();
foreach (BlogEssay essay in _essays.Values)
{
foreach (string tag in essay.Tags)
foreach (EssayTag essayTag in essay.Tags.Select(tag => new EssayTag(tag)))
{
if (_tags.TryGetValue(tag, out var list))
if (_tags.TryGetValue(essayTag, out List<BlogEssay>? essays))
{
list.Add(essay);
essays.Add(essay);
}
else
{
_tags[tag] = [essay];
_tags.Add(essayTag, [essay]);
}
}
}
}
foreach (KeyValuePair<string,List<BlogEssay>> pair in _tags)
public bool SearchByUrlEncodedTag(string tag, [NotNullWhen(true)] out List<BlogEssay>? result)
{
pair.Value.Sort();
}
}
result = (from item in _tags
where item.Key.UrlEncodedTagName == tag
select item.Value).FirstOrDefault();
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)
{
if (_tags.TryGetValue(tag, out var list))
{
return list;
}
throw new KeyNotFoundException("Selected tag not found.");
return result is not null;
}
}

View File

@ -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++)
{
char c = content.FileContent[i];
string rawContent = content.FileContent[..pos];
MatchCollection matches = DescriptionPattern().Matches(rawContent);
if (char.IsControl(c) || char.IsSymbol(c) ||
char.IsSeparator(c) || char.IsPunctuation(c) ||
char.IsAsciiLetter(c))
StringBuilder builder = new();
foreach (Match match in matches)
{
continue;
builder.Append(match.Value);
}
builder.Append(c);
if (breakSentence)
{
builder.Append("……");
}
string description = builder.ToString();
@ -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'秒'");
}
}

View 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();
}
}
}

View File

@ -19,6 +19,10 @@
<script src="_framework/blazor.web.js"></script>
<script src="bootstrap.bundle.min.js"></script>
<script src="clipboard.min.js"></script>
<script>
const clipboard = new ClipboardJS('.btn');
</script>
</body>
</html>

View File

@ -1,6 +1,8 @@
@using YaeBlog.Core.Services
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models
@inject EssayContentService EssayContentInstance
@inject IEssayContentService Contents
@inject BlogOptions Options
<div class="container">
<div class="row justify-content-center">
@ -22,7 +24,7 @@
<div class="col-auto">
<a href="/blog/archives">
@(EssayContentInstance.Essays.Count)
@(Contents.Essays.Count)
</a>
</div>
</div>
@ -34,9 +36,22 @@
<div class="col-auto">
<a href="/blog/tags">
@(EssayContentInstance.TagCount)
@(Contents.Tags.Count)
</a>
</div>
</div>
<div class="row justify-content-start fs-5" style="padding-top: 2em">
<div class="col-auto">
广而告之
</div>
</div>
<div class="row">
<div class="col">
<p style="text-indent: 2em">
@(Options.Announcement)
</p>
</div>
</div>
</div>

View File

@ -0,0 +1,36 @@
@using YaeBlog.Core.Models
@inject BlogOptions Options
<div class="row px-2 py-4 copyright border border-primary rounded-1 bg-primary-subtle">
<div class="col">
<div class="row p-1">
<div class="col">
文章作者:<a href="https://rrricardo.top" target="_blank">Ricardo Ren</a>
</div>
</div>
<div class="row p-1">
<div class="col">
文章地址:
<a href="/blog/essays/@(EssayAddress)" target="_blank">
@($"https://rrricardo.top/blog/essays/{EssayAddress}")
</a>
</div>
</div>
<div class="row p-1">
<div class="col">
版权声明:本博客所有文章除特别声明外,均采用
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a>
许可协议,转载请注明来自
<a href="https://rrricardo.top/blog/" target="_blank">Ricardo's Blog</a>。
</div>
</div>
</div>
</div>
@code
{
[Parameter] public string? EssayAddress { get; set; }
}

View File

@ -0,0 +1,2 @@
.copyright {
}

View File

@ -3,40 +3,54 @@
@attribute [StreamRendering]
<main class="container">
<div class="row" style="height: 80px">
<div class="px-2 col-8">
<div class="row d-none d-xl-flex" style="height: 80px">
<div class="px-2 col-9">
<a href="/blog/" class="p-2">
<h4>Ricardo's Blog</h4>
</a>
</div>
<div class="col-4 d-flex justify-content-around align-items-center">
<div class="col-3 d-flex justify-content-around align-items-center">
<a href="/blog/" class="p-2">
<div class="d-inline-flex" style="text-align: center">
<Icon Name="@IconName.House" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="@IconSize.x5"/>
<h5>首页</h5>
</div>
</a>
<a href="/blog/archives/" class="p-2">
<div class="d-inline-flex">
<Icon Name="@IconName.Archive" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="IconSize.x5"/>
<h5>归档</h5>
</div>
</a>
<a href="/blog/tags/" class="p-2">
<div class="d-inline-flex">
<Icon Name="@IconName.Tags" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="@IconSize.x5"/>
<h5>标签</h5>
</div>
</a>
<a href="/blog/about/" class="p-2">
<div class="d-inline-flex">
<Icon Name="@IconName.Person" Color="@IconColor.Primary" class="px-2 d-none d-xl-block" Size="@IconSize.x5"/>
<h5>关于</h5>
</a>
</div>
</div>
<div class="row d-xl-none">
<div class="px-2 col-12">
<a href="/blog/" class="p-2">
<h4>Ricardo's Blog</h4>
</a>
</div>
<div class="px-2 col-12 justify-content-end d-flex">
<a href="/blog/" class="p-2">
<h5>首页</h5>
</a>
<a href="/blog/archives/" class="p-2">
<h5>归档</h5>
</a>
<a href="/blog/tags/" class="p-2">
<h5>标签</h5>
</a>
<a href="/blog/about/" class="p-2">
<h5>关于</h5>
</a>
</div>
</div>

View File

@ -1,4 +1,11 @@
@page "/blog/about"
@using YaeBlog.Core.Models
@inject BlogOptions Options
<PageTitle>
关于
</PageTitle>
<div class="container">
<div class="row">
@ -8,12 +15,13 @@
</div>
<div class="row">
<div class="col fst-italic py-4">
把字刻在石头上!
<div class="col fst-italic py-2">
把字刻在石头上!(・’ω’・)
</div>
</div>
<div class="row p-2">
<div class="col">
<div class="row">
<div class="col">
<h3>关于我</h3>
@ -61,8 +69,10 @@
</div>
</div>
</div>
</div>
<div class="row p-2">
<div class="col">
<div class="row">
<div class="col">
<h3>关于本站</h3>
@ -84,6 +94,52 @@
</div>
</div>
<div class="row p-2">
<div class="col">
<div class="row">
<div class="col">
<h3>友链</h3>
</div>
</div>
<div class="row py-2">
<div class="col fst-italic">
欢迎所有人联系我添加友链!(´。✪ω✪。`)
</div>
</div>
<div class="row py-2">
@foreach (FriendLink link in Options.Links)
{
<div class="col-sm-12 col-md-4 col-lg-3">
<a href="@(link.Link)" target="_blank" class="m-3">
<div class="row link-item">
<div class="col-4">
<Image Src="@(link.AvatarImage)" Alt="@(link.Name)" Style="border-radius: 50%"/>
</div>
<div class="col-8">
<div class="row">
<div class="col-auto fs-5">
@(link.Name)
</div>
</div>
<div class="row">
<div class="col-auto fst-italic">
@(link.Description)
</div>
</div>
</div>
</div>
</a>
</div>
}
</div>
</div>
</div>
</div>
@code {
}

View File

@ -0,0 +1,8 @@
.link-item {
padding: 1rem;
border-radius: 4px;
}
.link-item:hover {
background-color: var(--bs-secondary-bg);
}

View File

@ -1,8 +1,12 @@
@page "/blog/archives"
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject EssayContentService EssayContentInstance
@inject IEssayContentService Contents
<PageTitle>
归档
</PageTitle>
<div class="container">
<div class="row">
@ -64,9 +68,8 @@
{
base.OnInitialized();
_essays.AddRange(from essay in EssayContentInstance.Essays
_essays.AddRange(from essay in Contents.Essays
orderby essay.Value.PublishTime descending
group essay by new DateTime(essay.Value.PublishTime.Year, 1, 1));
}
}

View File

@ -1,10 +1,14 @@
@page "/blog"
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject EssayContentService EssayContentInstance
@inject IEssayContentService Contents
@inject NavigationManager NavigationInstance
<PageTitle>
Ricardo's Blog
</PageTitle>
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-9">
@ -95,15 +99,15 @@
protected override void OnInitialized()
{
_page = Page ?? 1;
_pageCount = EssayContentInstance.Essays.Count / EssaysPerPage + 1;
_pageCount = Contents.Essays.Count / EssaysPerPage + 1;
if (EssaysPerPage * _page > EssayContentInstance.Essays.Count + EssaysPerPage)
if (EssaysPerPage * _page > Contents.Essays.Count + EssaysPerPage)
{
NavigationInstance.NavigateTo("/NotFount");
return;
}
_essays.AddRange(EssayContentInstance.Essays
_essays.AddRange(Contents.Essays
.OrderByDescending(p => p.Value.PublishTime)
.Skip((_page - 1) * EssaysPerPage)
.Take(EssaysPerPage));

View File

@ -1,9 +1,10 @@
@page "/blog/essays/{BlogKey}"
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject IEssayContentService Contents
@inject ITableOfContentService TableOfContent
@inject NavigationManager NavigationInstance
@inject EssayContentService EssayContentInstance
<PageTitle>
@(_essay!.Title)
@ -12,11 +13,11 @@
<div class="container py-4">
<div class="row">
<div class="col-auto">
<h1>@(_essay!.Title)</h1>
<h1 id="title">@(_essay!.Title)</h1>
</div>
</div>
<div class="row p-4">
<div class="row px-4 py-1">
<div class="col-auto fw-light">
@(_essay!.PublishTime.ToString("yyyy-MM-dd"))
</div>
@ -29,19 +30,87 @@
}
</div>
<div class="row px-4 py-1">
<div class="col-auto fw-light">
总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。
</div>
</div>
<div class="row">
<div class="col-lg-8 col-md-12">
@((MarkupString)_essay!.HtmlContent)
<LicenseDisclaimer EssayAddress="@BlogKey"/>
</div>
<div class="col-lg-4 col-md-12">
<div class="row sticky-lg-top justify-content-center">
<div class="col-auto">
<div class="row">
<div class="col-auto">
<h3 style="margin-block-start: 1em; margin-block-end: 0.5em">
文章目录
</h3>
</div>
</div>
<div class="row" style="padding-left: 10px">
<div class="col-auto">
@foreach (BlogHeadline level2 in _headline!.Children)
{
<div class="row py-1">
<div class="col-auto">
<a href="@(GenerateSelectorUrl(level2.SelectorId))">@(level2.Title)</a>
</div>
</div>
@foreach (BlogHeadline level3 in level2.Children)
{
<div class="row py-1">
<div class="col-auto">
<a style="padding-left: 20px" href="@GenerateSelectorUrl(level3.SelectorId)">
@(level3.Title)
</a>
</div>
</div>
@foreach (BlogHeadline level4 in level3.Children)
{
<div class="row py-1">
<div class="col-auto">
<a style="padding-left: 40px" href="@(GenerateSelectorUrl(level4.SelectorId))">
@(level4.Title)
</a>
</div>
</div>
}
}
}
</div>
</div>
@if (_headline!.Children.Count == 0)
{
<div class="row">
<div class="col fst-italic">
坏了(* Ŏ∀Ŏ),没有在文章中识别到目录
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
@code {
[Parameter]
public string? BlogKey { get; set; }
[Parameter] public string? BlogKey { get; set; }
private BlogEssay? _essay;
private BlogHeadline? _headline;
protected override void OnInitialized()
{
base.OnInitialized();
@ -52,10 +121,15 @@
return;
}
if (!EssayContentInstance.TryGet(BlogKey, out _essay))
if (!Contents.Essays.TryGetValue(BlogKey, out _essay))
{
NavigationInstance.NavigateTo("/NotFound");
}
_headline = TableOfContent.Headlines[BlogKey];
}
private string GenerateSelectorUrl(string selectorId)
=> $"/blog/essays/{BlogKey!}#{selectorId}";
}

View File

@ -1,12 +1,16 @@
@page "/"
<PageTitle>
Ricardo's Index
</PageTitle>
<div class="container">
<div class="row py-4">
<div class="col-4">
<div class="col-lg-4 col-12 p-5 p-lg-0">
<Image Src="images/avatar.png" Alt="Ricardo's Avatar"/>
</div>
<div class="col-8">
<div class="col-lg-8 col-12">
<div class="container px-3">
<div class="row">
<h4 class="fw-bold">初冬的朝阳 (Ricardo Ren)</h4>
@ -27,6 +31,7 @@
</div>
</div>
</div>
</div>
<div class="row" style="padding-top: 80px">
<p class="fs-5">恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。</p>
@ -45,8 +50,6 @@
如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。
</p>
</div>
</div>
</div>
@code {

View File

@ -1,5 +1,9 @@
@page "/NotFound"
<PageTitle>
啊~ 页面走丢啦~
</PageTitle>
<div class="container">
<h3>NotFound!</h3>
</div>

View File

@ -1,8 +1,14 @@
@page "/blog/tags/"
@using System.Text.Encodings.Web
@using YaeBlog.Core.Abstractions
@using YaeBlog.Core.Models
@using YaeBlog.Core.Services
@inject EssayContentService EssayContentInstance
@inject IEssayContentService Contents
@inject NavigationManager NavigationInstance
<PageTitle>
@(TagName ?? "标签")
</PageTitle>
<div class="container">
<div class="row">
@ -28,18 +34,19 @@
{
<div>
<ul>
@foreach (KeyValuePair<string, int> pair in EssayContentInstance.Tags)
@foreach (KeyValuePair<EssayTag, List<BlogEssay>> pair in
Contents.Tags.OrderByDescending(pair => pair.Value.Count))
{
<li class="p-2">
<a href="/blog/tags/?tagName=@(pair.Key)">
<a href="/blog/tags/?tagName=@(pair.Key.UrlEncodedTagName)">
<div class="container fs-5">
<div class="row">
<div class="col-auto">
# @(pair.Key)
# @(pair.Key.TagName)
</div>
<div class="col-auto tag-count">
@(pair.Value)
@(pair.Value.Count)
</div>
</div>
</div>
@ -52,7 +59,7 @@
else
{
<div class="container">
@foreach (BlogEssay essay in EssayContentInstance.GetTag(TagName).OrderByDescending(e => e.PublishTime))
@foreach (BlogEssay essay in _essays)
{
<EssayCard Essay="@essay"/>
}
@ -63,5 +70,23 @@
@code {
[SupplyParameterFromQuery] public string? TagName { get; set; }
private readonly List<BlogEssay> _essays = [];
protected override void OnInitialized()
{
base.OnInitialized();
if (string.IsNullOrEmpty(TagName))
{
return;
}
if (!Contents.SearchByUrlEncodedTag(UrlEncoder.Default.Encode(TagName), out List<BlogEssay>? essays))
{
NavigationInstance.NavigateTo("/NotFound");
return;
}
_essays.AddRange(essays.OrderDescending());
}
}

View File

@ -13,7 +13,7 @@ WebApplication application = builder.Build();
application.UseStaticFiles();
application.UseAntiforgery();
application.UseMiddleRenderProcessors();
application.UseYaeBlog();
application.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();

View File

@ -8,18 +8,8 @@
"AllowedHosts": "*",
"Blog": {
"Root": "source",
"Author": "Ricardo Ren",
"Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。",
"StartYear": 2021,
"ProjectName": "Blog",
"BannerImage": "images/banner.png",
"EssayImage": "images/banner.png",
"RegisterInformation": "蜀ICP备2022004429号-1",
"About": {
"Introduction": "A CS Student",
"Description": "还太菜了,没有做出太多的贡献。",
"AvatarImage": "images/avatar.png"
},
"Links": [
{
"Name": "Ichirinko",

View File

@ -5,8 +5,6 @@ tags:
- 随笔
---
# 2021年终总结
2021年已经过去2022年已经来临。每每一年开始的时候我都会展开一张纸或者新建一个文档思量着又是一年时光也该同诸大杂志一般写几句意味深长的话语怀念过去的时光也祝福未来的自己。可往往脑海中已是三万字的长篇落在笔头却又是一个字都没有了。
如今跨年的时候已经过去朋友圈中已经不见文案的踪影我也该重新提笔细说自己2021年中做过的种种。

View File

@ -6,8 +6,6 @@ date: 2022-12-30 14:58:12
---
# 2022年终总结
2022是困难的一年。我们需要为2023年做好准备。
<!--more-->

View File

@ -6,9 +6,6 @@ typora-root-url: 2022-summer-vacation
date: 2022-08-22 15:39:13
---
# 2022年暑假总结
在8个月的漫长寒假的最后两个月~~也就是俗称的暑假中~~,我都干了些什么?
<!--more-->

View File

@ -7,8 +7,6 @@ typora-root-url: big-homework
date: 2022-07-27 11:34:49
---
# 代码大作业初体验
在大学也呆了一年了,终于遇上了第一个需要多人合作的写代码项目。从四月底分组完成,任务部署下来到七月初接近尾声,在这两个多月的时间里,也算是经历了不少,学到了不少。
<!--more-->

View File

@ -7,10 +7,6 @@ tags:
---
# 建立博客过程的记录
## 博客之始
当我已经在Python的浩瀚大海遨zhengzha了半个暑假后我决定尝试一下传说中程序员专用的学(zhuang)习(bi)手(fangfa)段(fa)——建立自己的个人博客。作为一个半懂不懂的Python程序员心中冒出的第一个想法自然是采用Python的Django作为开发自己的个人博客的手段。然而在阅读了[用Django搭建个人博客](https://www.dusaiphoto.com/article/2/)等的其他人搭建这类动态博客的过程记录之后我便义无反顾的转向了采用javascript开发的博客框架[Hexo](https://hexo.io)<del>说好的Python信仰呢</del>。无他,唯简单尔。
<!--more-->

View File

@ -7,7 +7,6 @@ typora-root-url: c-include-problems
date: 2022-05-08 11:35:19
---
最近在完成一门`C`语言课程的大作业,课设老师要求我们将程序分模块的开发。在编写项目头文件的时候,遇到了一些令本菜鸡大开眼界的问题。
<!--more-->

View File

@ -6,8 +6,7 @@ tags:
date: 2022-11-11 22:20:25
---
# 编译MediaPipe框架
编译MediaPipe框架。
<!--more-->
最近开始研究自己的大创项目,一个关于动作捕捉的小玩意儿,第一步就是~~抄袭开源代码~~借鉴他人优秀成果。在众多的项目中我看上了这个Google开源的优秀框架先把这个项目在本地上跑起来再说。这篇文章就记录了我编译这个框架的过程。

View File

@ -7,9 +7,6 @@ date: 2023-01-15 22:23:08
typora-root-url: daily-linux-0
---
# 日用Linux挑战 第0篇
在将开发重心移到`WSL`上一年之后我最终还是决定完全抛弃Windows转向使用Linux作为我日常使用的主力系统。目前我已经使用Linux作为主力系统一个月了。
<!--more-->

View File

@ -7,8 +7,6 @@ date: 2023-03-08 22:37:29
---
# 日用Linux挑战第1篇
从去年12月底正式切换到`Linux`开始算起,我日常使用`Linux`已经过去了2个月的时间。在本系列的上一篇文章——[日用Linux挑战 第0篇 - Ricardo的博客](https://rrricardo.top/blog/2023/01/15/daily-linux-0/)中,我讲述了我配置自己的`Arch Linux`的过程,还小小的赞扬了一波`Linux`在近些年来取得的进展。但是在这篇文章中,我将重点指出日常使用过程中遇到的问题和困难。
<!--more-->

View File

@ -8,8 +8,6 @@ typora-root-url: daily-linux-2
---
# 日用Linux挑战 第2篇
使用`Linux`6个月我成功戒掉了原神。
<!--more-->

View File

@ -8,8 +8,6 @@ date: 2023-09-04 14:47:46
---
# 日用Linux挑战 第三篇
成也开源,败也开源。
<!--more-->

View File

@ -1,48 +0,0 @@
---
title: .net从入门到放弃再到入门
tags:
- 技术笔记
- dotnet
date: 2022-08-21 16:59:08
---
# .net从入门到放弃再到入门
我宣布下面的内容都是我在胡扯。在写了一学期的`Java`和`Springboot`之后,我的评价是`Java`是什么垃圾东西,`C#`才是永远的神!
~~这是一篇专业的.net劝退指南详细记述了我为啥选择了.net又为啥选择了放弃。~~
<!--more-->
### 开端——WPF桌面应用
~~在今年年初的时候,受到[通知大全](https://squidward.top/)启发我决定编写一款自己的DDL管理应用程序。在一开始我打算先在Windows平台上编写当时我了解到Windows上最正统的桌面应用程序开发方式就是采用微软自家的.net平台开发同时在[知乎](https://zhihu.com)上一群人在吹`C#`在设计上是如何如何的优于`JAVA`,当然在这里没有说他们的观点错误的意思,把我忽悠的一愣一愣的。.net在当时已经有了比较老旧的`WinForms`框架、比较流行的`WPF`框架和最新的`MAUI`框架。~~秉持着中国人中庸的思想潮流~~,我选择了`WPF`图形框架作为我当时开发的框架,从此入了.net平台的坑。~~
> 如今这个桌面端的项目已经基本烂尾,项目开源在[github](https://github.com/jackfiled/PostCalendarWindows),算是警醒后来的我在技术选型时应该更加的慎重
~~说实话,在开发的一开始,我就感觉力不从心。~~
~~第一官方文档好但不完全好。微软虽然提供了本地化过的文档但是有很大比例都是机翻的文档。我觉得吧放机翻的文档还不如直接放英文原文的文档。同时文档的大部分都是API列表一类的参考资料对于初学者来说比较重要的“实用教程”等等部分内容较少虽然文档全面而详细但对于初学者来说并没有很大的帮助。毕竟我们不知道在数以万计的API中哪个才能实现自己的需求。~~
~~第二,国内缺少.net相关的社区氛围。这点也是最为劝退的地方相比于`JAVA`在国内广泛的应用和丰富的社区内容,国内.net和`C#`相关的内容除了在`Unity`游戏开发中还算广泛,其他称得上是乏善可陈。~~
~~在一堆好几年前的博客和半懂不懂的英文文档中沉浮一个月之后,我总算是写出了一个可以运行的成品。随着新学期的到来,以学业繁忙为借口,我停止了这个应用的开发。~~
### 重启——服务器开发
~~在经历了桌面应用的失败之后,我转移了自己的开发重心,开始了移动应用的开发,这次我没有坚持被微软的`MAUI`那八字还没有一撇的技术忽悠进去,选择了`flutter`框架搞开发。有应用自然就得有提供数据的后端,在开发服务器时,我又被微软的`ASP.NET`给“忽悠”了进去。~~
~~平心而论,`ASP.NET`开发的过程比上文中的桌面应用程序的开发还是要顺利不少。毕竟现在采用`B/S`架构的服务更多,个人感觉国内应用这项技术的人也不少,相关的技术资料也就不少,我入门的过程也就流畅许多。~~
~~虽然但是,在开发的过程中劝退的地方也不少。第一是微软自己的版本更迭,尤其是在微软宣布`dotnet core`,将.net开源之后个人感觉国内的开发者似乎不是很感冒还是用着原来的老一套。第二是和现在的技术流行方向不同原本采用`JAVA`搞服务器的开发不太可能再更换语言,第二新入行的开发者也会选择当下流行的`GO`等语言,这就导致在开发中有一种单打独斗的感觉,很难找到人同你合作。~~
### 放弃
~~在坚持学习.net八个月之后我还是决定放弃转向学习`JAVA`。~~
- ~~没人用的技术的技术不要硬刚,即使是好技术也不要。没人用的技术就像一潭死水,只有流动起来,才能孕育生机和活力,否则只能在原地腐烂。~~
- ~~之于我而言,语言不仅仅是开发软件的工具,更是需要计算机相关知识的工具。转向`JAVA`的原因之一就是我目前学习的数据结合课——伯克利的`CS61b`就是采用`JAVA`作为编程语言的。~~
~~虽然我已经决定将我的主力语言转向`JAVA`~~但不得不承认`C#`在语言上的特性还是很不错的,以后我的重心不会放在`C#`上,但是他也不会从的技能树上消失。也许在若干年之后,我的主力语言又变回`C#`了(笑)。
> 全文的最后一句话可能是我现在唯一认同的了

View File

@ -6,8 +6,6 @@ tags:
typora-root-url: 环境配置
---
# 环境配置备忘录
电脑上的环境三天两头出问题,写下一个备忘录记录一下电脑上环境的配置过程。
<!--more-->

View File

@ -5,7 +5,7 @@ tags:
date: 2022-12-31 13:38:19
---
# 原神抽卡研究一
实际上是“概率论和随机过程”课程的期末小论文。
<!--more-->

View File

@ -7,7 +7,7 @@ tags:
typora-root-url: 安装pytorch来有深度的学习
---
# 深度学习预备篇——安装深度学习框架——pytorch
鄙人在下不才我精通深度学习框架的安装和卸载。
<!--more-->

View File

@ -7,8 +7,6 @@ date: 2022-06-13 16:17:27
---
# 大学生用啥配置——计算机专业
> 本文是应B站UP主[远古时代装机猿](https://space.bilibili.com/35359510)发起的[大学生用啥配置](https://www.bilibili.com/video/BV1kZ4y1i7Le)公益活动而写
>
> 目前某不知名211大学计算机专业在读

View File

@ -6,9 +6,6 @@ tags:
date: 2023-10-09 23:56:34
---
# 解决云原神无法在Linux中浏览器下运行的问题
本文为转载`bilibili`用户[@SocialismTeen](https://space.bilibili.com/33027704)在他的[专栏](https://www.bilibili.com/read/cv26576757)中给出的解决办法。
<!--more-->

View File

@ -6,10 +6,10 @@ tags:
date: 2024-1-12 20:10:06
---
<!--more-->
让Minecraft游戏使用`Wayland`显示协议。
<!--more-->
## Update At 2024-2-24
在两天前2024年的2月22日`glfw`释出了一个新版本`3.4`。在新版本中,`glfw`大幅强化了对于`wayland`显示协议的支持,在默认情况下就会直接使用`wayland`显示协议。但是为了能够正常的运行`minecraft`,还需要对源代码进行修改:

View File

@ -1,54 +0,0 @@
---
title: PostCalendar介绍
typora-root-url: PostCalendar介绍
date: 2022-03-05 14:18:50
tags:
---
# PostCalendar介绍
> 本文是[PostCalendarWindows](https://https://github.com/jackfiled/PostCalendarWindows)README文件的国内镜像
一款集日程管理与DDL管理于一身的日历软件。
下载地址:[GithubRelease](https://github.com/jackfiled/PostCalendarWindows/releases)
# 开发未完成,仍在内测阶段
<!--more-->
## 支持的功能
### 日历部分
![日历部分截图](Calendar.png)
1. 支持日历事件的添加,删除,修改。
2. 支持读取教务处自动生成的excel课表文件。
> 这个功能需要电脑上安装excel应用程序.
>
> 目前这个功能仅作实验性的支持不保证excel读取的完全准确。
>
> > 使用帮助在教务处网站“学期理论课表”页面有打印按钮可以下载一个excel表格。
> >
> > 下载完成后在软件里点击“导入excel课表”按钮选择excel文件下载的位置即可自动导入。
### DDL部分
![DDL部分截图](DDL.png)
1. 支持DDL事件的添加修改完成删除。
2. 可以将活动界面中DDL类型的事件直接添加到个人DDL中。
### 活动部分
![活动部分截图](Activity.png)
**本部分未完成,设想中将与[DDL网站](http://squidward.top)的数据进行同步**
## 使用方法
软件依赖于.net6.0,请点击[链接](https://dotnet.microsoft.com/zh-cn/download/dotnet/thank-you/runtime-desktop-6.0.2-windows-x64-installer)下载.net6.0运行时。
在安装完成或者确认电脑上已安装.net6.0运行时之后点击下载旁边的release包双击下载文件中的PostCalendarWindows.exe即可使用。

BIN
YaeBlog/source/post-calendar/Activity.png (Stored with Git LFS)

Binary file not shown.

BIN
YaeBlog/source/post-calendar/Calendar.png (Stored with Git LFS)

Binary file not shown.

BIN
YaeBlog/source/post-calendar/DDL.png (Stored with Git LFS)

Binary file not shown.

View File

@ -5,8 +5,6 @@ tags:
- 学习资料
---
# 程序设计与计算导论笔记
直接扒的老黄的PPT ,简直毫无参考价值。
不保证笔记的绝对正确性。
如果导致考试爆炸,不承担任何责任。

View File

@ -7,8 +7,6 @@ typora-root-url: qt-learning
date: 2022-07-01 14:32:39
---
# 初学Qt的一点小笔记
最近的大作业需要用 `C/C++`的技术栈实现一个图形化界面,`Qt`作为C++图形化框架久负盛名,正好借着这个写大作业的机会学习一下这个应用广泛的框架。
<!--more-->

View File

@ -5,7 +5,6 @@ tags:
- 技术笔记
---
# 安装Visual Studio 2019中遇到的坑
在某个月黑风高的夜晚我在折腾了很久的Python之后突然感觉自己应该去学学C和C++于是乎我便打算折腾一下在vscode上写C和C++。在网上一番搜寻之后,我发现了这篇[知乎文章](https://zhuanlan.zhihu.com/p/87864677)和这篇[知乎文章](https://zhuanlan.zhihu.com/p/147366852),然后我就被安装MinGW编译器和配置一大堆的json文件给干碎了。<br>
于是我决定转向传说中的宇宙第一IDE——Visual Studio。<br>
<!--more-->

View File

@ -6,8 +6,7 @@ tags:
date: 2023-07-29 15:20:02
---
# SpringBoot自定义注解实现权限控制
如题。
<!--more-->

View File

@ -5,7 +5,7 @@ tags:
- 技术笔记
---
# 优雅地使用学校VPN
睿智的Global Protect!
<!--more-->

View File

@ -5,7 +5,6 @@ tags:
- 技术笔记
---
# 无论何处都可以使用自己定制的VSCode开发环境
众所周知VSCode作为大微软家开发的开源编辑器一经发布便受到了两广大程序员群体的欢迎。如果我们深入的了解一下VSCode就会知道VSCode是基于Electron框架构建的Web应用程序而Electron框架是基于Web技术来开发桌面应用程序即VSCode只要稍加改造就可以流畅的在浏览器中运行。那么我们如何才能在浏览器打开一个VSCode呢
最简单的方法是在浏览器中输入[这个网址](https://vscode.dev)就可以在浏览器中打开一个VSCode Online这个版本的VSCode可以支持打开本地的文件并且进行编辑。不过这个编辑器并不支持大部分的插件而且并不支持程序的编译与运行并不是一个可以开箱即用的编辑器。那么还有什么办法可以让我们拥有一个联网即可得的个人定制化开发环境呢
<!--more-->

View File

@ -7,9 +7,6 @@ typora-root-url: wsl-setup-csapp
date: 2022-09-03 19:02:58
---
# 利用WSL设置CSAPP实验环境
`CSAPP`这本书为自学的学生们提供了不少的`LAB`供大家在联系中提高,但是这些`LAB`的编写普遍需要一个`Linux`的实验环境,但是目前大多数人手中的环境都是`Windows`平台,没有办法原生的运行这些`LAB`。在以前的实践中,这个问题往往是通过安装虚拟机来解决的,但是现在我们有了更好的解决方案——`Windows Subsystem for Linux`,简称`WSL`。
<!--more-->

7
YaeBlog/wwwroot/clipboard.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,3 @@
body a {
text-decoration: none;
}
body main {
flex: 1;
min-height: 100vh;
overflow-x: visible;
}
@font-face {
font-family: "Font Awesome 6 Free";
font-style: normal;
@ -33,3 +23,68 @@ body main {
border-radius: 4px;
background-color: transparent;
}
.table-wrapper {
overflow-x: auto;
}
body a {
text-decoration: none;
}
body main {
flex: 1;
min-height: 100vh;
overflow-x: visible;
}
body p {
margin: 0;
margin-block: 1em;
}
h2, h3 {
margin-block-start: 1.5em;
margin-block-end: 1em;
}
h4, h5 {
margin-block-start: 1em;
margin-block-end: 1em;
}
table {
margin: 0 auto;
border-collapse: collapse;
}
table td {
padding: 3px 20px;
border: 1px var(--bs-border-color) solid;
}
table thead {
background: var(--bs-secondary-bg);
}
table thead td {
font-weight: 700;
border: none;
}
table thead th {
padding: 3px 20px;
}
table thead tr {
border: 1px var(--bs-border-color) solid;
}
blockquote {
margin: 20px 0;
padding: 0 20px;
color: var(--bs-body-color);
background-color: var(--bs-primary-bg-subtle);
border-block-start: .1em solid var(--bs-primary-border-subtle);
border-block-end: .1em solid var(--bs-primary-border-subtle);
}

View File

@ -1,8 +0,0 @@
import * as fluentUI
from '/_content/Microsoft.FluentUI.AspNetCore.Components/Microsoft.FluentUI.AspNetCore.Components.lib.module.js';
fluentUI.typeRampBaseFontSize.withDefault("16px");
fluentUI.typeRampBaseLineHeight.withDefault("16px");