Skip to content
Open
Show file tree
Hide file tree
Changes from all 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; }
}

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