Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support variables in table of contents. #458

Merged
merged 3 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/testing/nested/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
---
sub:
x: "Variable"
---
# Testing Nesting

The files in this directory are used for testing purposes. Do not edit these files unless you are working on tests.
The files in this directory are used for testing purposes. Do not edit these files unless you are working on tests.


## Injecting a {{x}} is supported in headers.

This should show up in the file's table of contents too.
8 changes: 6 additions & 2 deletions src/Elastic.Markdown/Helpers/Interpolation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ internal static partial class InterpolationRegex

public static class Interpolation
{
public static bool ReplaceSubstitutions(this ReadOnlySpan<char> span, Dictionary<string, string>? properties, out string? replacement)
public static bool ReplaceSubstitutions(this ReadOnlySpan<char> span, IReadOnlyDictionary<string, string>? properties, out string? replacement)
{
replacement = null;
if (span.IndexOf("}}") < 0)
return false;

var substitutions = properties ?? new();
if (properties is null || properties.Count == 0)
return false;

var substitutions = properties as Dictionary<string, string>
?? new Dictionary<string, string>(properties, StringComparer.OrdinalIgnoreCase);
if (substitutions.Count == 0)
return false;

Expand Down
94 changes: 51 additions & 43 deletions src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using YamlDotNet.Serialization;

namespace Elastic.Markdown.IO;

Expand Down Expand Up @@ -106,70 +107,54 @@ public async Task<MarkdownDocument> ParseFullAsync(Cancel ctx)
return document;
}

private void ReadDocumentInstructions(MarkdownDocument document)
private IReadOnlyDictionary<string, string> GetSubstitutions()
{
var globalSubstitutions = MarkdownParser.Configuration.Substitutions;
var fileSubstitutions = YamlFrontMatter?.Properties;
if (fileSubstitutions is not { Count: >= 0 })
return globalSubstitutions;

var allProperties = new Dictionary<string, string>(fileSubstitutions);
foreach (var (key, value) in globalSubstitutions)
allProperties[key] = value;
return allProperties;
}

private void ReadDocumentInstructions(MarkdownDocument document)
{
Title = document
.FirstOrDefault(block => block is HeadingBlock { Level: 1 })?
.GetData("header") as string;

if (document.FirstOrDefault() is YamlFrontMatterBlock yaml)
{
var raw = string.Join(Environment.NewLine, yaml.Lines.Lines);
YamlFrontMatter = ReadYamlFrontMatter(raw);

// TODO remove when migration tool and our demo content sets are updated
var deprecatedTitle = YamlFrontMatter.Title;
if (!string.IsNullOrEmpty(deprecatedTitle))
{
Collector.EmitWarning(FilePath, "'title' is no longer supported in yaml frontmatter please use a level 1 header instead.");
// TODO remove fallback once migration is over and we fully deprecate front matter titles
if (string.IsNullOrEmpty(Title))
Title = deprecatedTitle;
}
YamlFrontMatter = ProcessYamlFrontMatter(document);
NavigationTitle = YamlFrontMatter.NavigationTitle;

var subs = GetSubstitutions();

// set title on yaml front matter manually.
// frontmatter gets passed around as page information throughout
YamlFrontMatter.Title = Title;

NavigationTitle = YamlFrontMatter.NavigationTitle;
if (!string.IsNullOrEmpty(NavigationTitle))
{
var props = MarkdownParser.Configuration.Substitutions;
var properties = YamlFrontMatter.Properties;
if (properties is { Count: >= 0 } local)
{
var allProperties = new Dictionary<string, string>(local);
foreach (var (key, value) in props)
allProperties[key] = value;
if (NavigationTitle.AsSpan().ReplaceSubstitutions(allProperties, out var replacement))
NavigationTitle = replacement;
}
else
{
if (NavigationTitle.AsSpan().ReplaceSubstitutions(properties, out var replacement))
NavigationTitle = replacement;
}
}
if (!string.IsNullOrEmpty(NavigationTitle))
{
if (NavigationTitle.AsSpan().ReplaceSubstitutions(subs, out var replacement))
NavigationTitle = replacement;
}
else
YamlFrontMatter = new YamlFrontMatter { Title = Title };

if (string.IsNullOrEmpty(Title))
{
Title = RelativePath;
Collector.EmitWarning(FilePath, "Document has no title, using file name as title.");
}
else if (Title.AsSpan().ReplaceSubstitutions(subs, out var replacement))
Title = replacement;

var contents = document
.Descendants<HeadingBlock>()
.Where(block => block is { Level: >= 2 })
.Select(h => (h.GetData("header") as string, h.GetData("anchor") as string))
.Select(h => new PageTocItem
.Select(h =>
{
Heading = h.Item1!.StripMarkdown(),
Slug = (h.Item2 ?? h.Item1).Slugify()
var header = h.Item1!.StripMarkdown();
if (header.AsSpan().ReplaceSubstitutions(subs, out var replacement))
header = replacement;
return new PageTocItem { Heading = header!, Slug = (h.Item2 ?? header).Slugify() };
})
.ToList();

Expand All @@ -192,6 +177,29 @@ private void ReadDocumentInstructions(MarkdownDocument document)
_instructionsParsed = true;
}

private YamlFrontMatter ProcessYamlFrontMatter(MarkdownDocument document)
{
if (document.FirstOrDefault() is not YamlFrontMatterBlock yaml)
return new YamlFrontMatter { Title = Title };

var raw = string.Join(Environment.NewLine, yaml.Lines.Lines);
var fm = ReadYamlFrontMatter(raw);

// TODO remove when migration tool and our demo content sets are updated
var deprecatedTitle = fm.Title;
if (!string.IsNullOrEmpty(deprecatedTitle))
{
Collector.EmitWarning(FilePath, "'title' is no longer supported in yaml frontmatter please use a level 1 header instead.");
// TODO remove fallback once migration is over and we fully deprecate front matter titles
if (string.IsNullOrEmpty(Title))
Title = deprecatedTitle;
}
// set title on yaml front matter manually.
// frontmatter gets passed around as page information throughout
fm.Title = Title;
return fm;
}

private YamlFrontMatter ReadYamlFrontMatter(string raw)
{
try
Expand Down