Skip to content

Commit de02ce1

Browse files
authored
Support variables in table of contents. (#458)
* Support variables in table of contents. * Replace variables in h1 titles correctly as well * dotnet format
1 parent d6bfa89 commit de02ce1

File tree

3 files changed

+67
-46
lines changed

3 files changed

+67
-46
lines changed

docs/testing/nested/index.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
---
2+
sub:
3+
x: "Variable"
4+
---
15
# Testing Nesting
26

3-
The files in this directory are used for testing purposes. Do not edit these files unless you are working on tests.
7+
The files in this directory are used for testing purposes. Do not edit these files unless you are working on tests.
8+
9+
10+
## Injecting a {{x}} is supported in headers.
11+
12+
This should show up in the file's table of contents too.

src/Elastic.Markdown/Helpers/Interpolation.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ internal static partial class InterpolationRegex
1414

1515
public static class Interpolation
1616
{
17-
public static bool ReplaceSubstitutions(this ReadOnlySpan<char> span, Dictionary<string, string>? properties, out string? replacement)
17+
public static bool ReplaceSubstitutions(this ReadOnlySpan<char> span, IReadOnlyDictionary<string, string>? properties, out string? replacement)
1818
{
1919
replacement = null;
2020
if (span.IndexOf("}}") < 0)
2121
return false;
2222

23-
var substitutions = properties ?? new();
23+
if (properties is null || properties.Count == 0)
24+
return false;
25+
26+
var substitutions = properties as Dictionary<string, string>
27+
?? new Dictionary<string, string>(properties, StringComparer.OrdinalIgnoreCase);
2428
if (substitutions.Count == 0)
2529
return false;
2630

src/Elastic.Markdown/IO/MarkdownFile.cs

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Markdig;
1414
using Markdig.Extensions.Yaml;
1515
using Markdig.Syntax;
16+
using YamlDotNet.Serialization;
1617

1718
namespace Elastic.Markdown.IO;
1819

@@ -106,70 +107,54 @@ public async Task<MarkdownDocument> ParseFullAsync(Cancel ctx)
106107
return document;
107108
}
108109

109-
private void ReadDocumentInstructions(MarkdownDocument document)
110+
private IReadOnlyDictionary<string, string> GetSubstitutions()
110111
{
112+
var globalSubstitutions = MarkdownParser.Configuration.Substitutions;
113+
var fileSubstitutions = YamlFrontMatter?.Properties;
114+
if (fileSubstitutions is not { Count: >= 0 })
115+
return globalSubstitutions;
116+
117+
var allProperties = new Dictionary<string, string>(fileSubstitutions);
118+
foreach (var (key, value) in globalSubstitutions)
119+
allProperties[key] = value;
120+
return allProperties;
121+
}
111122

123+
private void ReadDocumentInstructions(MarkdownDocument document)
124+
{
112125
Title = document
113126
.FirstOrDefault(block => block is HeadingBlock { Level: 1 })?
114127
.GetData("header") as string;
115128

116-
if (document.FirstOrDefault() is YamlFrontMatterBlock yaml)
117-
{
118-
var raw = string.Join(Environment.NewLine, yaml.Lines.Lines);
119-
YamlFrontMatter = ReadYamlFrontMatter(raw);
120-
121-
// TODO remove when migration tool and our demo content sets are updated
122-
var deprecatedTitle = YamlFrontMatter.Title;
123-
if (!string.IsNullOrEmpty(deprecatedTitle))
124-
{
125-
Collector.EmitWarning(FilePath, "'title' is no longer supported in yaml frontmatter please use a level 1 header instead.");
126-
// TODO remove fallback once migration is over and we fully deprecate front matter titles
127-
if (string.IsNullOrEmpty(Title))
128-
Title = deprecatedTitle;
129-
}
129+
YamlFrontMatter = ProcessYamlFrontMatter(document);
130+
NavigationTitle = YamlFrontMatter.NavigationTitle;
130131

132+
var subs = GetSubstitutions();
131133

132-
// set title on yaml front matter manually.
133-
// frontmatter gets passed around as page information throughout
134-
YamlFrontMatter.Title = Title;
135-
136-
NavigationTitle = YamlFrontMatter.NavigationTitle;
137-
if (!string.IsNullOrEmpty(NavigationTitle))
138-
{
139-
var props = MarkdownParser.Configuration.Substitutions;
140-
var properties = YamlFrontMatter.Properties;
141-
if (properties is { Count: >= 0 } local)
142-
{
143-
var allProperties = new Dictionary<string, string>(local);
144-
foreach (var (key, value) in props)
145-
allProperties[key] = value;
146-
if (NavigationTitle.AsSpan().ReplaceSubstitutions(allProperties, out var replacement))
147-
NavigationTitle = replacement;
148-
}
149-
else
150-
{
151-
if (NavigationTitle.AsSpan().ReplaceSubstitutions(properties, out var replacement))
152-
NavigationTitle = replacement;
153-
}
154-
}
134+
if (!string.IsNullOrEmpty(NavigationTitle))
135+
{
136+
if (NavigationTitle.AsSpan().ReplaceSubstitutions(subs, out var replacement))
137+
NavigationTitle = replacement;
155138
}
156-
else
157-
YamlFrontMatter = new YamlFrontMatter { Title = Title };
158139

159140
if (string.IsNullOrEmpty(Title))
160141
{
161142
Title = RelativePath;
162143
Collector.EmitWarning(FilePath, "Document has no title, using file name as title.");
163144
}
145+
else if (Title.AsSpan().ReplaceSubstitutions(subs, out var replacement))
146+
Title = replacement;
164147

165148
var contents = document
166149
.Descendants<HeadingBlock>()
167150
.Where(block => block is { Level: >= 2 })
168151
.Select(h => (h.GetData("header") as string, h.GetData("anchor") as string))
169-
.Select(h => new PageTocItem
152+
.Select(h =>
170153
{
171-
Heading = h.Item1!.StripMarkdown(),
172-
Slug = (h.Item2 ?? h.Item1).Slugify()
154+
var header = h.Item1!.StripMarkdown();
155+
if (header.AsSpan().ReplaceSubstitutions(subs, out var replacement))
156+
header = replacement;
157+
return new PageTocItem { Heading = header!, Slug = (h.Item2 ?? header).Slugify() };
173158
})
174159
.ToList();
175160

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

180+
private YamlFrontMatter ProcessYamlFrontMatter(MarkdownDocument document)
181+
{
182+
if (document.FirstOrDefault() is not YamlFrontMatterBlock yaml)
183+
return new YamlFrontMatter { Title = Title };
184+
185+
var raw = string.Join(Environment.NewLine, yaml.Lines.Lines);
186+
var fm = ReadYamlFrontMatter(raw);
187+
188+
// TODO remove when migration tool and our demo content sets are updated
189+
var deprecatedTitle = fm.Title;
190+
if (!string.IsNullOrEmpty(deprecatedTitle))
191+
{
192+
Collector.EmitWarning(FilePath, "'title' is no longer supported in yaml frontmatter please use a level 1 header instead.");
193+
// TODO remove fallback once migration is over and we fully deprecate front matter titles
194+
if (string.IsNullOrEmpty(Title))
195+
Title = deprecatedTitle;
196+
}
197+
// set title on yaml front matter manually.
198+
// frontmatter gets passed around as page information throughout
199+
fm.Title = Title;
200+
return fm;
201+
}
202+
195203
private YamlFrontMatter ReadYamlFrontMatter(string raw)
196204
{
197205
try

0 commit comments

Comments
 (0)