Skip to content

Commit 5bccabc

Browse files
theletterfMpdreamz
andauthored
Add crosslinks to toc: in docset.yml (#1615)
* Add crosslinks to toc * Fix errors * Update docs * Add title validation * Add ctx for Cancel * FileNavigationItem can be ignored * Remove redundant code * Move routine * Fix resolution * Fix hx-select-oob for nav crosslinks * Add validation and title as mandatory * Add utility class for crosslink validation * Remove redundant file * Refactor NavCrossLinkValidator * Ensure we inject docs-builder on CI for integration tests as well * allow docs-builder to have local checkout folder on CI * Ensure we hadnle CrossLinkNavigationItem when building the sitemap by ignoring them * Remove `Fetch` from CrossLinkResolver, enforce eager fetching of crosslinks (#1784) * Remove `Fetch` from CrossLinkResolver, enforce eager fetching of crosslinks. This removes a hidden requirement on `DocumentationSet` that its resolver is not usable until `DocumentationGenerator.GenerateAll()` has been called. We now enforce `DocumentationSet` receives a `ICrossLinkResolver` that is ready to resolve crosslinks. * Found more cases of unhandled navigation types * Remove new caching behavior in DocSetConfigurationCrossLinkFetcher --------- Co-authored-by: Martijn Laarman <[email protected]>
1 parent 85d8541 commit 5bccabc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+537
-223
lines changed

docs-builder.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=crosslink/@EntryIndexedValue">True</s:Boolean>
23
<s:Boolean x:Key="/Default/UserDictionary/Words/=docset/@EntryIndexedValue">True</s:Boolean>
34
<s:Boolean x:Key="/Default/UserDictionary/Words/=frontmatter/@EntryIndexedValue">True</s:Boolean>
45
<s:Boolean x:Key="/Default/UserDictionary/Words/=linenos/@EntryIndexedValue">True</s:Boolean>

docs/_docset.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ toc:
129129
- file: req.md
130130
- folder: nested
131131
- file: cross-links.md
132+
children:
133+
- title: "Getting Started Guide"
134+
crosslink: docs-content://get-started/introduction.md
132135
- file: custom-highlighters.md
133136
- hidden: archive.md
134137
- hidden: landing-page.md
@@ -153,4 +156,4 @@ toc:
153156
- file: bar.md
154157
- folder: baz
155158
children:
156-
- file: qux.md
159+
- file: qux.md

docs/configure/content-set/navigation.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,38 @@ cross_links:
7272
- docs-content
7373
```
7474

75+
#### Adding cross-links in Markdown content
76+
7577
To link to a document in the `docs-content` repository, you would write the link as follows:
7678

77-
```
79+
```markdown
7880
[Link to docs-content doc](docs-content://directory/another-directory/file.md)
7981
```
8082

83+
You can also link to specific anchors within the document:
84+
85+
```markdown
86+
[Link to specific section](docs-content://directory/file.md#section-id)
87+
```
88+
89+
#### Adding cross-links in navigation
90+
91+
Cross-links can also be included in navigation structures. When creating a `toc.yml` file or defining navigation in `docset.yml`, you can add cross-links as follows:
92+
93+
```yaml
94+
toc:
95+
- file: index.md
96+
- title: External Documentation
97+
crosslink: docs-content://directory/file.md
98+
- folder: local-section
99+
children:
100+
- file: index.md
101+
- title: API Reference
102+
crosslink: elasticsearch://api/index.html
103+
```
104+
105+
Cross-links in navigation will be automatically resolved during the build process, maintaining consistent linking between related documentation across repositories.
106+
81107
### `exclude`
82108

83109
Files to exclude from the TOC. Supports glob patterns.

src/Elastic.ApiExplorer/Landing/LandingNavigationItem.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class LandingNavigationItem : IApiGroupingNavigationItem<ApiLanding, INav
3333
public IReadOnlyCollection<INavigationItem> NavigationItems { get; set; } = [];
3434
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
3535
public int NavigationIndex { get; set; }
36+
public bool IsCrossLink => false; // API landing items are never cross-links
3637
public string Url { get; }
3738
public bool Hidden => false;
3839

@@ -83,6 +84,7 @@ public abstract class ApiGroupingNavigationItem<TGroupingModel, TNavigationItem>
8384
public bool Hidden => false;
8485
/// <inheritdoc />
8586
public int NavigationIndex { get; set; }
87+
public bool IsCrossLink => false; // API grouping items are never cross-links
8688

8789
/// <inheritdoc />
8890
public int Depth => 0;
@@ -141,6 +143,7 @@ public class EndpointNavigationItem(ApiEndpoint endpoint, IRootNavigationItem<IA
141143

142144
/// <inheritdoc />
143145
public int NavigationIndex { get; set; }
146+
public bool IsCrossLink => false; // API endpoint items are never cross-links
144147

145148
/// <inheritdoc />
146149
public int Depth => 0;

src/Elastic.ApiExplorer/Operations/OperationNavigationItem.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,6 @@ IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem> parent
7070
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
7171

7272
public int NavigationIndex { get; set; }
73+
public bool IsCrossLink => false; // API operations are never cross-links
7374

7475
}

src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ public static AssemblyConfiguration Deserialize(string yaml, bool skipPrivateRep
3131
config.ReferenceRepositories[name] = repository;
3232
}
3333

34-
// if we are not running in CI, and we are skipping private repositories, and we can locate the solution directory. build the local docs-content repository
35-
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CI"))
36-
&& skipPrivateRepositories
34+
// If we are skipping private repositories, and we can locate the solution directory. include the local docs-content repository
35+
// this allows us to test new docset features as part of the assembler build
36+
if (skipPrivateRepositories
3737
&& config.ReferenceRepositories.TryGetValue("docs-builder", out var docsContentRepository)
3838
&& Paths.GetSolutionDirectory() is { } solutionDir
3939
)

src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.InteropServices;
77
using Elastic.Documentation.Configuration.Plugins.DetectionRules.TableOfContents;
88
using Elastic.Documentation.Configuration.TableOfContents;
9+
using Elastic.Documentation.Links;
910
using Elastic.Documentation.Navigation;
1011
using YamlDotNet.RepresentationModel;
1112

@@ -129,6 +130,8 @@ private IReadOnlyCollection<ITocItem> ReadChildren(YamlStreamReader reader, KeyV
129130
private IEnumerable<ITocItem>? ReadChild(YamlStreamReader reader, YamlMappingNode tocEntry, string parentPath)
130131
{
131132
string? file = null;
133+
string? crossLink = null;
134+
string? title = null;
132135
string? folder = null;
133136
string[]? detectionRules = null;
134137
TableOfContentsConfiguration? toc = null;
@@ -148,6 +151,19 @@ private IReadOnlyCollection<ITocItem> ReadChildren(YamlStreamReader reader, KeyV
148151
hiddenFile = key == "hidden";
149152
file = ReadFile(reader, entry, parentPath);
150153
break;
154+
case "title":
155+
title = reader.ReadString(entry);
156+
break;
157+
case "crosslink":
158+
hiddenFile = false;
159+
crossLink = reader.ReadString(entry);
160+
// Validate crosslink URI early
161+
if (!CrossLinkValidator.IsValidCrossLink(crossLink, out var errorMessage))
162+
{
163+
reader.EmitError(errorMessage!, tocEntry);
164+
crossLink = null; // Reset to prevent further processing
165+
}
166+
break;
151167
case "folder":
152168
folder = ReadFolder(reader, entry, parentPath);
153169
parentPath += $"{Path.DirectorySeparatorChar}{folder}";
@@ -165,6 +181,22 @@ private IReadOnlyCollection<ITocItem> ReadChildren(YamlStreamReader reader, KeyV
165181
}
166182
}
167183

184+
// Validate that crosslink entries have titles
185+
if (crossLink is not null && string.IsNullOrWhiteSpace(title))
186+
{
187+
reader.EmitError($"Cross-link entries must have a 'title' specified. Cross-link: {crossLink}", tocEntry);
188+
return null;
189+
}
190+
191+
// Validate that standalone titles (without content) are not allowed
192+
if (!string.IsNullOrWhiteSpace(title) &&
193+
file is null && crossLink is null && folder is null && toc is null &&
194+
(detectionRules is null || detectionRules.Length == 0))
195+
{
196+
reader.EmitError($"Table of contents entries with only a 'title' are not allowed. Entry must specify content (file, crosslink, folder, or toc). Title: '{title}'", tocEntry);
197+
return null;
198+
}
199+
168200
if (toc is not null)
169201
{
170202
foreach (var f in toc.Files)
@@ -199,6 +231,14 @@ private IReadOnlyCollection<ITocItem> ReadChildren(YamlStreamReader reader, KeyV
199231
return [new FileReference(this, path, hiddenFile, children ?? [])];
200232
}
201233

234+
if (crossLink is not null)
235+
{
236+
if (Uri.TryCreate(crossLink, UriKind.Absolute, out var crossUri) && CrossLinkValidator.IsCrossLink(crossUri))
237+
return [new CrossLinkReference(this, crossUri, title, hiddenFile, children ?? [])];
238+
else
239+
reader.EmitError($"Cross-link '{crossLink}' is not a valid absolute URI format", tocEntry);
240+
}
241+
202242
if (folder is not null)
203243
{
204244
if (children is null)

src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public interface ITocItem
1414
public record FileReference(ITableOfContentsScope TableOfContentsScope, string RelativePath, bool Hidden, IReadOnlyCollection<ITocItem> Children)
1515
: ITocItem;
1616

17+
public record CrossLinkReference(ITableOfContentsScope TableOfContentsScope, Uri CrossLinkUri, string? Title, bool Hidden, IReadOnlyCollection<ITocItem> Children)
18+
: ITocItem;
19+
1720
public record FolderReference(ITableOfContentsScope TableOfContentsScope, string RelativePath, IReadOnlyCollection<ITocItem> Children)
1821
: ITocItem;
1922

src/Elastic.Documentation.Site/Navigation/INavigationItem.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public interface INavigationItem
3434
bool Hidden { get; }
3535

3636
int NavigationIndex { get; set; }
37+
38+
/// Gets whether this navigation item is a cross-link to another repository.
39+
bool IsCrossLink { get; }
3740
}
3841

3942
/// Represents a leaf node in the navigation tree with associated model data.

src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,11 @@
7979
}
8080
else if (item is ILeafNavigationItem<INavigationModel> leaf)
8181
{
82+
var hasSameTopLevelGroup = !leaf.IsCrossLink && (Model.IsPrimaryNavEnabled && leaf.NavigationRoot.Id == Model.RootNavigationId || true);
8283
<li class="flex group/li pr-8 @(isTopLevel ? "font-semibold mt-6" : "mt-4")">
8384
<a
8485
href="@leaf.Url"
85-
@Htmx.GetNavHxAttributes(Model.IsPrimaryNavEnabled && leaf.NavigationRoot.Id == Model.RootNavigationId || true)
86+
@Htmx.GetNavHxAttributes(hasSameTopLevelGroup)
8687
class="sidebar-link grow group-[.current]/li:text-blue-elastic!"
8788
>
8889
@leaf.NavigationTitle

0 commit comments

Comments
 (0)