Skip to content

Commit 2efb415

Browse files
committed
Fix breadcrumbs data
1 parent 91c0ddc commit 2efb415

File tree

5 files changed

+82
-37
lines changed

5 files changed

+82
-37
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: 31 additions & 2 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,7 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
113114
fullNavigationRenderResult
114115
);
115116

117+
var structuredBreadcrumbsJson = CreateStructuredBreadcrumbsData(markdown, parents);
116118

117119
var slice = Page.Index.Create(new IndexViewModel
118120
{
@@ -147,7 +149,8 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
147149
LegacyPages = legacyPages?.Skip(1).ToArray(),
148150
VersionDropdownItems = VersionDrownDownItemViewModel.FromLegacyPageMappings(legacyPages?.Skip(1).ToArray()),
149151
Products = allProducts,
150-
VersionsConfig = DocumentationSet.Context.VersionsConfiguration
152+
VersionsConfig = DocumentationSet.Context.VersionsConfiguration,
153+
StructuredBreadcrumbsJson = structuredBreadcrumbsJson
151154
});
152155

153156
return new RenderResult
@@ -159,6 +162,33 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
159162

160163
}
161164

165+
private string CreateStructuredBreadcrumbsData(MarkdownFile markdown, INavigationItem[] parents)
166+
{
167+
List<BreadcrumbListItem> breadcrumbItems = [];
168+
var position = 1;
169+
var crumbs = parents.Reverse().DistinctBy(i => i.Url).ToList();
170+
// Add parents
171+
breadcrumbItems.AddRange(crumbs.Select((parent) => new BreadcrumbListItem
172+
{
173+
Position = position++,
174+
Name = parent.NavigationTitle,
175+
Item = new Uri(DocumentationSet.Context.CanonicalBaseUrl ?? new Uri("http://localhost"), Path.Combine(DocumentationSet.Context.UrlPathPrefix ?? string.Empty, parent.Url)).ToString()
176+
}));
177+
// Add current page
178+
breadcrumbItems.Add(new BreadcrumbListItem
179+
{
180+
Position = position,
181+
Name = markdown.Title ?? "[TITLE NOT SET]",
182+
Item = null,
183+
});
184+
var breadcrumbsList = new BreadcrumbsList
185+
{
186+
ItemListElement = breadcrumbItems
187+
};
188+
var structuredBreadcrumbsJson = JsonSerializer.Serialize(breadcrumbsList, BreadcrumbsContext.Default.BreadcrumbsList);
189+
return structuredBreadcrumbsJson.Trim();
190+
}
191+
162192
public async Task<MarkdownDocument> WriteAsync(IDirectoryInfo outBaseDir, IFileInfo outputFile, MarkdownFile markdown, IConversionCollector? collector, Cancel ctx = default)
163193
{
164194
if (outputFile.Directory is { Exists: false })
@@ -203,5 +233,4 @@ public record RenderResult
203233
public required string Html { get; init; }
204234
public required string FullNavigationPartialHtml { get; init; }
205235
public required string NavigationFileName { get; init; }
206-
207236
}

src/Elastic.Markdown/Layout/_Breadcrumbs.cshtml

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,25 @@
66
: Model.Parents.Reverse().Skip(1);
77

88
//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();
9+
var crumbs = targets.DistinctBy(p => p.Url).ToList();
1210
}
1311

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-
12+
<ol id="breadcrumbs" class="flex flex-wrap gap-1 items-center w-full py-6">
3713
@for (var i = 0; i < crumbs.Count; i++)
3814
{
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>
15+
var item = crumbs[i];
16+
<li class="text-ink-light text-sm leading-[1.2em]">
4217
<a
43-
itemprop="item"
18+
itemprop="item"
4419
href="@item.Url"
4520
@Htmx.GetHxAttributes(Model.CurrentNavigationItem?.NavigationRoot.Id == item.NavigationRoot.Id)
4621
>
47-
<span itemprop="name" class="hover:text-black">@item.NavigationTitle</span>
22+
<span class="hover:text-black">@item.NavigationTitle</span>
4823
</a>
49-
<meta itemprop="position" content="@(i+2)">
24+
@if (i < crumbs.Count - 1)
25+
{
26+
<span class="px-1">/</span>
27+
}
5028
</li>
5129
}
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>
5530
</ol>

src/Elastic.Markdown/Page/Index.cshtml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
<meta class="elastic" name="product_version" content="@(new HtmlString(Model.CurrentVersion))"/>
5151
<meta name="DC.identifier" content="@(new HtmlString(Model.CurrentVersion))"/>
5252
<link rel="alternate" type="text/markdown" href="@(Model.MarkdownUrl)" title="Markdown export"/>
53+
<script type="application/ld+json">
54+
@(new HtmlString(Model.StructuredBreadcrumbsJson))
55+
</script>
5356
}
5457
if (name == GlobalSections.Head && Model.Products is { Count: > 0 })
5558
{

src/Elastic.Markdown/Page/IndexViewModel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public class IndexViewModel
5757
public required HashSet<Product> Products { get; init; }
5858

5959
public required VersionsConfiguration VersionsConfig { get; init; }
60+
61+
// https://developers.google.com/search/docs/appearance/structured-data/breadcrumb#json-ld
62+
public required string StructuredBreadcrumbsJson { get; init; }
6063
}
6164

6265
public class VersionDrownDownItemViewModel

0 commit comments

Comments
 (0)