Add support for headers in markdown

pull/1037/head
Tyrrrz 1 year ago
parent d8315c7827
commit 469a731892

@ -91,6 +91,25 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
return result;
}
protected override async ValueTask<MarkdownNode> VisitHeaderAsync(
HeaderNode header,
CancellationToken cancellationToken = default)
{
_buffer.Append(
// lang=html
$"<h{header.Level}>"
);
var result = await base.VisitHeaderAsync(header, cancellationToken);
_buffer.Append(
// lang=html
$"</h{header.Level}>"
);
return result;
}
protected override async ValueTask<MarkdownNode> VisitInlineCodeBlockAsync(
InlineCodeBlockNode inlineCodeBlock,
CancellationToken cancellationToken = default)

@ -758,6 +758,37 @@
overflow-wrap: break-word;
}
.chatlog__markdown h1 {
margin-block: 0;
margin-top: 1rem;
margin-bottom: 0.5rem;
color: @Themed("#f2f3f5", "#060607");
font-size: 1.5rem;
line-height: 1;
}
.chatlog__markdown h2 {
margin-block: 0;
margin-top: 1rem;
margin-bottom: 0.5rem;
color: @Themed("#f2f3f5", "#060607");
font-size: 1.25rem;
line-height: 1;
}
.chatlog__markdown h3 {
margin-block: 0;
margin-top: 1rem;
margin-bottom: 0.5rem;
color: @Themed("#f2f3f5", "#060607");
font-size: 1rem;
line-height: 1;
}
.chatlog__markdown h1:first-child, h2:first-child, h3:first-child {
margin-top: 0.5rem;
}
.chatlog__markdown-preserve {
white-space: pre-wrap;
}

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace DiscordChatExporter.Core.Markdown;
internal record HeaderNode(
int Level,
IReadOnlyList<MarkdownNode> Children
) : MarkdownNode, IContainerNode;

@ -81,15 +81,15 @@ internal static partial class MarkdownParser
private static readonly IMatcher<MarkdownNode> SingleLineQuoteNodeMatcher = new RegexMatcher<MarkdownNode>(
// Capture any character until the end of the line.
// Opening 'greater than' character must be followed by whitespace.
// Text content is optional.
new Regex(@"^>\s(.*\n?)", DefaultRegexOptions),
// Consume the newline character so that it's not included in the content.
new Regex(@"^>\s(.+\n?)", DefaultRegexOptions),
(s, m) => new FormattingNode(FormattingKind.Quote, Parse(s.Relocate(m.Groups[1])))
);
private static readonly IMatcher<MarkdownNode> RepeatedSingleLineQuoteNodeMatcher = new RegexMatcher<MarkdownNode>(
// Repeatedly capture any character until the end of the line.
// This one is tricky as it ends up producing multiple separate captures which need to be joined.
new Regex(@"(?:^>\s(.*\n?)){2,}", DefaultRegexOptions),
// Consume the newline character so that it's not included in the content.
new Regex(@"(?:^>\s(.+\n?)){2,}", DefaultRegexOptions),
(_, m) => new FormattingNode(
FormattingKind.Quote,
Parse(
@ -106,6 +106,16 @@ internal static partial class MarkdownParser
(s, m) => new FormattingNode(FormattingKind.Quote, Parse(s.Relocate(m.Groups[1])))
);
/* Headers */
private static readonly IMatcher<MarkdownNode> HeaderNodeMatcher = new RegexMatcher<MarkdownNode>(
// Capture any character until the end of the line.
// Opening 'hash' character(s) must be followed by whitespace.
// Consume the newline character so that it's not included in the content.
new Regex(@"^(\#{1,3})\s(.+\n?)", DefaultRegexOptions),
(s, m) => new HeaderNode(m.Groups[1].Length, Parse(s.Relocate(m.Groups[2])))
);
/* Code blocks */
private static readonly IMatcher<MarkdownNode> InlineCodeBlockNodeMatcher = new RegexMatcher<MarkdownNode>(
@ -330,6 +340,9 @@ internal static partial class MarkdownParser
RepeatedSingleLineQuoteNodeMatcher,
SingleLineQuoteNodeMatcher,
// Headers
HeaderNodeMatcher,
// Code blocks
MultiLineCodeBlockNodeMatcher,
InlineCodeBlockNodeMatcher,

@ -20,6 +20,14 @@ internal abstract class MarkdownVisitor
return formatting;
}
protected virtual async ValueTask<MarkdownNode> VisitHeaderAsync(
HeaderNode header,
CancellationToken cancellationToken = default)
{
await VisitAsync(header.Children, cancellationToken);
return header;
}
protected virtual ValueTask<MarkdownNode> VisitInlineCodeBlockAsync(
InlineCodeBlockNode inlineCodeBlock,
CancellationToken cancellationToken = default) =>
@ -63,6 +71,9 @@ internal abstract class MarkdownVisitor
FormattingNode formatting =>
await VisitFormattingAsync(formatting, cancellationToken),
HeaderNode header =>
await VisitHeaderAsync(header, cancellationToken),
InlineCodeBlockNode inlineCodeBlock =>
await VisitInlineCodeBlockAsync(inlineCodeBlock, cancellationToken),

Loading…
Cancel
Save