Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
22 changes: 22 additions & 0 deletions src/Elastic.Documentation.Links/CrossLinks/CrossLinkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Elastic.Documentation.Links.CrossLinks;
public interface ICrossLinkResolver
{
bool TryResolve(Action<string> errorEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri);
bool TryGetLinkMetadata(Uri crossLinkUri, [NotNullWhen(true)] out LinkMetadata? linkMetadata);
IUriEnvironmentResolver UriResolver { get; }
}

Expand All @@ -24,6 +25,13 @@ public bool TryResolve(Action<string> errorEmitter, Uri crossLinkUri, [NotNullWh
return false;
}

/// <inheritdoc />
public bool TryGetLinkMetadata(Uri crossLinkUri, [NotNullWhen(true)] out LinkMetadata? linkMetadata)
{
linkMetadata = null;
return false;
}

/// <inheritdoc />
public IUriEnvironmentResolver UriResolver { get; } = new IsolatedBuildEnvironmentUriResolver();

Expand All @@ -39,6 +47,20 @@ public class CrossLinkResolver(FetchedCrossLinks crossLinks, IUriEnvironmentReso
public bool TryResolve(Action<string> errorEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri) =>
TryResolve(errorEmitter, _crossLinks, UriResolver, crossLinkUri, out resolvedUri);

public bool TryGetLinkMetadata(Uri crossLinkUri, [NotNullWhen(true)] out LinkMetadata? linkMetadata)
{
linkMetadata = null;

if (!_crossLinks.LinkReferences.TryGetValue(crossLinkUri.Scheme, out var sourceLinkReference))
return false;

var originalLookupPath = (crossLinkUri.Host + '/' + crossLinkUri.AbsolutePath.TrimStart('/')).Trim('/');
if (string.IsNullOrEmpty(originalLookupPath) && crossLinkUri.Host.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
originalLookupPath = crossLinkUri.Host;

return sourceLinkReference.Links.TryGetValue(originalLookupPath, out linkMetadata);
}

public FetchedCrossLinks UpdateLinkReference(string repository, RepositoryLinks repositoryLinks)
{
var dictionary = _crossLinks.LinkReferences.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Expand Down
4 changes: 4 additions & 0 deletions src/Elastic.Documentation/Links/RepositoryLinks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public record LinkMetadata
[JsonPropertyName("hidden")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool Hidden { get; init; }

[JsonPropertyName("title")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Title { get; init; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot remove this property and associated changes in CrossLinkResolver

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 6b2c4ca. Removed the Title property from LinkMetadata and the TryGetLinkMetadata method from ICrossLinkResolver and all implementations. The behavior now simply emits an error when encountering empty crosslink text, without trying to fetch or store titles.

}

public record LinkSingleRedirect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ private static void ProcessCrossLink(LinkInline link, InlineProcessor processor,
uri, out var resolvedUri)
)
link.Url = resolvedUri.ToString();

// Handle empty link text by trying to get title from crosslink metadata
if (link.FirstChild == null && context.CrossLinkResolver.TryGetLinkMetadata(uri, out var linkMetadata))
{
var title = linkMetadata.Title;
if (!string.IsNullOrEmpty(title))
_ = link.AppendChild(new LiteralInline(title));
}
}

private static void ProcessInternalLink(LinkInline link, InlineProcessor processor, ParserContext context)
Expand Down
50 changes: 50 additions & 0 deletions tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,56 @@ public void EmitsCrossLink()
}
}

public class CrossLinkEmptyTextTest(ITestOutputHelper output) : LinkTestBase(output,
"""

Go to [](kibana://index.md)
"""
)
{
[Fact]
public void GeneratesHtml() =>
// language=html
Html.Should().Contain(
"""<p>Go to <a href="https://docs-v3-preview.elastic.dev/elastic/kibana/tree/main/" hx-select-oob="#main-container" preload="mousedown">Kibana Guide</a></p>"""
);

[Fact]
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);

[Fact]
public void EmitsCrossLink()
{
Collector.CrossLinks.Should().HaveCount(1);
Collector.CrossLinks.Should().Contain("kibana://index.md");
}
}

public class CrossLinkEmptyTextNoTitleTest(ITestOutputHelper output) : LinkTestBase(output,
"""

Go to [](kibana://get-started/index.md)
"""
)
{
[Fact]
public void GeneratesHtml() =>
// language=html - when no title is available, link text should remain empty
Html.Should().Contain(
"""<p>Go to <a href="https://docs-v3-preview.elastic.dev/elastic/kibana/tree/main/get-started" hx-select-oob="#main-container" preload="mousedown"></a></p>"""
);

[Fact]
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);

[Fact]
public void EmitsCrossLink()
{
Collector.CrossLinks.Should().HaveCount(1);
Collector.CrossLinks.Should().Contain("kibana://get-started/index.md");
}
}

public class LinkWithUnresolvedInterpolationError(ITestOutputHelper output) : LinkTestBase(output,
"""
[global search field]({{this-variable-does-not-exist}}/introduction.html#kibana-navigation-search)
Expand Down
10 changes: 9 additions & 1 deletion tests/Elastic.Markdown.Tests/TestCrossLinkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ public TestCrossLinkResolver()
"url_path_prefix": "/elastic/docs-content/tree/main",
"cross_links": [],
"links": {
"index.md": {},
"index.md": {
"title": "Kibana Guide"
},
"get-started/index.md": {
"anchors": [
"elasticsearch-intro-elastic-stack",
Expand Down Expand Up @@ -68,4 +70,10 @@ public TestCrossLinkResolver()

public bool TryResolve(Action<string> errorEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri) =>
CrossLinkResolver.TryResolve(errorEmitter, _crossLinks, UriResolver, crossLinkUri, out resolvedUri);

public bool TryGetLinkMetadata(Uri crossLinkUri, [NotNullWhen(true)] out LinkMetadata? linkMetadata)
{
var resolver = new CrossLinkResolver(_crossLinks, UriResolver);
return resolver.TryGetLinkMetadata(crossLinkUri, out linkMetadata);
}
}
4 changes: 4 additions & 0 deletions tests/authoring/Framework/TestCrossLinkResolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,8 @@ type TestCrossLinkResolver (config: ConfigurationFile) =
member this.TryResolve(errorEmitter, crossLinkUri, [<Out>]resolvedUri : byref<Uri|null>) =
CrossLinkResolver.TryResolve(errorEmitter, crossLinks, uriResolver, crossLinkUri, &resolvedUri)

member this.TryGetLinkMetadata(crossLinkUri, [<Out>]linkMetadata : byref<LinkMetadata|null>) =
let resolver = new CrossLinkResolver(crossLinks, uriResolver)
resolver.TryGetLinkMetadata(crossLinkUri, &linkMetadata)


Loading