Skip to content

Commit a280475

Browse files
authored
Fix breadcrumbs data (#1879)
* Fix breadcrumbs data * Fixes * Refactor * Cleanup * Also move comment
1 parent 93205fc commit a280475

File tree

6 files changed

+90
-50
lines changed

6 files changed

+90
-50
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Text.Json.Serialization;
6+
7+
namespace Elastic.Markdown;
8+
9+
// Model structure based on https://developers.google.com/search/docs/appearance/structured-data/breadcrumb#json-ld
10+
public record BreadcrumbsList
11+
{
12+
[JsonPropertyName("@context")]
13+
public string Context => "https://schema.org";
14+
[JsonPropertyName("@type")]
15+
public string Type => "BreadcrumbList";
16+
[JsonPropertyName("itemListElement")]
17+
public required List<BreadcrumbListItem> ItemListElement { get; init; }
18+
}
19+
20+
public record BreadcrumbListItem
21+
{
22+
[JsonPropertyName("@type")]
23+
public string Type => "ListItem";
24+
[JsonPropertyName("position")]
25+
public required int Position { get; init; }
26+
[JsonPropertyName("name")]
27+
public required string Name { get; init; }
28+
29+
[JsonPropertyName("item")]
30+
public string? Item { get; init; }
31+
}
32+
33+
[JsonSourceGenerationOptions(WriteIndented = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
34+
[JsonSerializable(typeof(BreadcrumbsList))]
35+
public sealed partial class BreadcrumbsContext : JsonSerializerContext;

src/Elastic.Markdown/HtmlWriter.cs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.IO.Abstractions;
6+
using System.Text.Json;
67
using Elastic.Documentation;
78
using Elastic.Documentation.Configuration.Builder;
89
using Elastic.Documentation.Legacy;
@@ -113,6 +114,11 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
113114
fullNavigationRenderResult
114115
);
115116

117+
//TODO should we even distinctby
118+
var breadcrumbs = parents.Reverse().DistinctBy(p => p.Url).ToArray();
119+
var breadcrumbsList = CreateStructuredBreadcrumbsData(markdown, breadcrumbs);
120+
var structuredBreadcrumbsJsonString = JsonSerializer.Serialize(breadcrumbsList, BreadcrumbsContext.Default.BreadcrumbsList);
121+
116122

117123
var slice = Page.Index.Create(new IndexViewModel
118124
{
@@ -124,12 +130,11 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
124130
TitleRaw = markdown.TitleRaw ?? "[TITLE NOT SET]",
125131
MarkdownHtml = html,
126132
PageTocItems = [.. markdown.PageTableOfContent.Values],
127-
Tree = DocumentationSet.Tree,
128133
CurrentDocument = markdown,
129134
CurrentNavigationItem = current,
130135
PreviousDocument = previous,
131136
NextDocument = next,
132-
Parents = parents,
137+
Breadcrumbs = breadcrumbs,
133138
NavigationHtml = navigationHtmlRenderResult.Html,
134139
NavigationFileName = navigationFileName,
135140
UrlPathPrefix = markdown.UrlPathPrefix,
@@ -147,7 +152,8 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
147152
LegacyPages = legacyPages?.Skip(1).ToArray(),
148153
VersionDropdownItems = VersionDrownDownItemViewModel.FromLegacyPageMappings(legacyPages?.Skip(1).ToArray()),
149154
Products = allProducts,
150-
VersionsConfig = DocumentationSet.Context.VersionsConfiguration
155+
VersionsConfig = DocumentationSet.Context.VersionsConfiguration,
156+
StructuredBreadcrumbsJson = structuredBreadcrumbsJsonString
151157
});
152158

153159
return new RenderResult
@@ -159,6 +165,31 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
159165

160166
}
161167

168+
private BreadcrumbsList CreateStructuredBreadcrumbsData(MarkdownFile markdown, INavigationItem[] crumbs)
169+
{
170+
List<BreadcrumbListItem> breadcrumbItems = [];
171+
var position = 1;
172+
// Add parents
173+
breadcrumbItems.AddRange(crumbs.Select(parent => new BreadcrumbListItem
174+
{
175+
Position = position++,
176+
Name = parent.NavigationTitle,
177+
Item = new Uri(DocumentationSet.Context.CanonicalBaseUrl ?? new Uri("http://localhost"), Path.Combine(DocumentationSet.Context.UrlPathPrefix ?? string.Empty, parent.Url)).ToString()
178+
}));
179+
// Add current page
180+
breadcrumbItems.Add(new BreadcrumbListItem
181+
{
182+
Position = position,
183+
Name = markdown.Title ?? markdown.NavigationTitle,
184+
Item = null,
185+
});
186+
var breadcrumbsList = new BreadcrumbsList
187+
{
188+
ItemListElement = breadcrumbItems
189+
};
190+
return breadcrumbsList;
191+
}
192+
162193
public async Task<MarkdownDocument> WriteAsync(IDirectoryInfo outBaseDir, IFileInfo outputFile, MarkdownFile markdown, IConversionCollector? collector, Cancel ctx = default)
163194
{
164195
if (outputFile.Directory is { Exists: false })
@@ -203,5 +234,4 @@ public record RenderResult
203234
public required string Html { get; init; }
204235
public required string FullNavigationPartialHtml { get; init; }
205236
public required string NavigationFileName { get; init; }
206-
207237
}

src/Elastic.Markdown/Layout/_Breadcrumbs.cshtml

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,27 @@
22
@{
33
var targets =
44
Model.Features.PrimaryNavEnabled
5-
? Model.Parents.Reverse()
6-
: Model.Parents.Reverse().Skip(1);
7-
8-
//TODO should we even distinctby
9-
var parents = targets.DistinctBy(p => p.Url).ToList();
10-
var firstCrumb = parents.FirstOrDefault();
11-
var crumbs = parents.Skip(1).TakeLast(2).ToList();
5+
? Model.Breadcrumbs
6+
: Model.Breadcrumbs.Skip(1);
7+
var crumbs = targets.ToList();
128
}
139

14-
<ol id="breadcrumbs" class="block items-center w-full py-6" itemscope="" itemtype="https://schema.org/BreadcrumbList">
15-
@if (firstCrumb != null)
16-
{
17-
<li class="inline text-ink-light text-sm leading-[1.2em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
18-
<a
19-
hx-disable="true"
20-
itemprop="item"
21-
href="@firstCrumb.Url"
22-
@Htmx.GetHxAttributes( Model.CurrentNavigationItem?.NavigationRoot.Id == firstCrumb.NavigationRoot.Id)
23-
>
24-
<span itemprop="name" class="hover:text-black">@firstCrumb.NavigationTitle</span>
25-
</a>
26-
<meta itemprop="position" content="1">
27-
</li>
28-
}
29-
@if (crumbs.Count > 0 && parents.Count > 3)
30-
{
31-
<li class="inline text-ink-light text-sm leading-[1.2em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
32-
<span class="px-1">/</span>
33-
34-
</li>
35-
}
36-
10+
<ol id="breadcrumbs" class="flex flex-wrap gap-1 items-center w-full py-6">
3711
@for (var i = 0; i < crumbs.Count; i++)
3812
{
39-
var item = crumbs[i];
40-
<li class="inline text-ink-light text-sm leading-[1.2em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
41-
<span class="px-1">/</span>
13+
var item = crumbs[i];
14+
<li class="text-ink-light text-sm leading-[1.2em]">
4215
<a
43-
itemprop="item"
16+
itemprop="item"
4417
href="@item.Url"
4518
@Htmx.GetHxAttributes(Model.CurrentNavigationItem?.NavigationRoot.Id == item.NavigationRoot.Id)
4619
>
47-
<span itemprop="name" class="hover:text-black">@item.NavigationTitle</span>
20+
<span class="hover:text-black">@item.NavigationTitle</span>
4821
</a>
49-
<meta itemprop="position" content="@(i+2)">
22+
@if (i < crumbs.Count - 1)
23+
{
24+
<span class="px-1">/</span>
25+
}
5026
</li>
5127
}
52-
<li class="inline text-ink-light text-sm leading-[1.2em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
53-
<span class="px-1">/</span>
54-
</li>
5528
</ol>

src/Elastic.Markdown/MarkdownLayoutViewModel.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ public record MarkdownLayoutViewModel : GlobalLayoutViewModel
1717
public required bool HideEditThisPage { get; init; }
1818
public required string? ReportIssueUrl { get; init; }
1919

20-
public required INavigationItem[] Parents { get; init; }
21-
22-
public required LegacyPageMapping[]? LegacyPages { get; init; }
20+
public required INavigationItem[] Breadcrumbs { get; init; }
2321

2422
public required IReadOnlyCollection<PageTocItem> PageTocItems { get; init; }
2523

src/Elastic.Markdown/Page/Index.cshtml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
CurrentNavigationItem = Model.CurrentNavigationItem,
2525
Previous = Model.PreviousDocument,
2626
Next = Model.NextDocument,
27-
Parents = Model.Parents,
2827
NavigationHtml = Model.NavigationHtml,
2928
NavigationFileName = Model.NavigationFileName,
3029
UrlPathPrefix = Model.UrlPathPrefix,
@@ -37,7 +36,7 @@
3736
Features = Model.Features,
3837
StaticFileContentHashProvider = Model.StaticFileContentHashProvider,
3938
ReportIssueUrl = Model.ReportIssueUrl,
40-
LegacyPages = Model.LegacyPages,
39+
Breadcrumbs = Model.Breadcrumbs,
4140
CurrentVersion = Model.CurrentDocument.YamlFrontMatter?.Layout == MarkdownPageLayout.LandingPage ? Model.VersionsConfig.VersioningSystems[0].Current : Model.CurrentVersion,
4241
AllVersionsUrl = Model.AllVersionsUrl,
4342
VersionDropdownSerializedModel = JsonSerializer.Serialize(Model.VersionDropdownItems,
@@ -50,6 +49,9 @@
5049
<meta class="elastic" name="product_version" content="@(new HtmlString(Model.CurrentVersion))"/>
5150
<meta name="DC.identifier" content="@(new HtmlString(Model.CurrentVersion))"/>
5251
<link rel="alternate" type="text/markdown" href="@(Model.MarkdownUrl)" title="Markdown export"/>
52+
<script type="application/ld+json">
53+
@(new HtmlString(Model.StructuredBreadcrumbsJson))
54+
</script>
5355
}
5456
if (name == GlobalSections.Head && Model.Products is { Count: > 0 })
5557
{

src/Elastic.Markdown/Page/IndexViewModel.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,13 @@ public class IndexViewModel
2424
public required string Description { get; init; }
2525
public required string TitleRaw { get; init; }
2626
public required string MarkdownHtml { get; init; }
27-
public required DocumentationGroup Tree { get; init; }
2827
public required IReadOnlyCollection<PageTocItem> PageTocItems { get; init; }
2928
public required MarkdownFile CurrentDocument { get; init; }
3029

3130
public required INavigationItem CurrentNavigationItem { get; init; }
3231
public required INavigationItem? PreviousDocument { get; init; }
3332
public required INavigationItem? NextDocument { get; init; }
34-
public required INavigationItem[] Parents { get; init; }
33+
public required INavigationItem[] Breadcrumbs { get; init; }
3534

3635
public required string NavigationHtml { get; init; }
3736

@@ -57,6 +56,9 @@ public class IndexViewModel
5756
public required HashSet<Product> Products { get; init; }
5857

5958
public required VersionsConfiguration VersionsConfig { get; init; }
59+
60+
// https://developers.google.com/search/docs/appearance/structured-data/breadcrumb#json-ld
61+
public required string StructuredBreadcrumbsJson { get; init; }
6062
}
6163

6264
public class VersionDrownDownItemViewModel

0 commit comments

Comments
 (0)