Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/Elastic.ApiExplorer/OpenApiGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private async Task<IFileInfo> Render<T>(INavigationItem current, T page, ApiRend
if (!outputFile.Directory!.Exists)
outputFile.Directory.Create();

var navigationHtml = await navigationRenderer.RenderNavigation(current.NavigationRoot, new Uri("http://ignored.example"), ctx);
var navigationHtml = await navigationRenderer.RenderNavigation(current.NavigationRoot, new Uri("http://ignored.example"), INavigationHtmlWriter.AllLevels, ctx);
renderContext = renderContext with
{
CurrentNavigation = current,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public bool PrimaryNavEnabled
set => _featureFlags["primary-nav"] = value;
}

public bool LazyLoadNavigation
{
get => IsEnabled("LAZY_LOAD_NAVIGATION");
set => _featureFlags["LAZY_LOAD_NAVIGATION"] = value;
}

private bool IsEnabled(string key)
{
var envKey = $"FEATURE_{key.ToUpperInvariant().Replace('-', '_')}";
Expand Down
5 changes: 5 additions & 0 deletions src/Elastic.Documentation.Site/Layout/_PagesNav.cshtml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@inherits RazorSlice<Elastic.Documentation.Site.GlobalLayoutViewModel>
<aside class="sidebar bg-white fixed md:sticky shadow-2xl md:shadow-none left-[100%] group-has-[#pages-nav-hamburger:checked]/body:left-0 bottom-0 md:left-auto pl-6 md:pl-2 top-[calc(var(--offset-top)+1px)] w-[80%] md:w-auto shrink-0 border-r-1 border-r-grey-20 z-40 md:z-auto">

@if (Model.Features.LazyLoadNavigation)
{
<div hx-get="@(Model.CurrentNavigationItem.Url + (Model.CurrentNavigationItem.Url.EndsWith('/') ? "index.nav.html" : "/index.nav.html"))" hx-trigger="load" hx-params="nav" hx-push-url="false" hx-swap="innerHTML" hx-target="#pages-nav"></div>
}
<nav
id="pages-nav"
class="sidebar-nav h-full">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ namespace Elastic.Documentation.Site.Navigation;

public interface INavigationHtmlWriter
{
Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, Cancel ctx = default);
const int AllLevels = -1;

Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, int maxLevel, Cancel ctx = default);

async Task<string> Render(NavigationViewModel model, Cancel ctx)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ namespace Elastic.Documentation.Site.Navigation;
public class IsolatedBuildNavigationHtmlWriter(BuildContext context, IRootNavigationItem<INavigationModel, INavigationItem> siteRoot)
: INavigationHtmlWriter
{
private readonly ConcurrentDictionary<string, string> _renderedNavigationCache = [];
private readonly ConcurrentDictionary<(string, int), string> _renderedNavigationCache = [];

public async Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, Cancel ctx = default)
public async Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, int maxLevel, Cancel ctx = default)
{
var navigation = context.Configuration.Features.PrimaryNavEnabled || currentRootNavigation.IsUsingNavigationDropdown
? currentRootNavigation
: siteRoot;

if (_renderedNavigationCache.TryGetValue(navigation.Id, out var value))
if (_renderedNavigationCache.TryGetValue((navigation.Id, maxLevel), out var value))
return value;

var model = CreateNavigationModel(navigation);
var model = CreateNavigationModel(navigation, maxLevel);
value = await ((INavigationHtmlWriter)this).Render(model, ctx);
_renderedNavigationCache[navigation.Id] = value;
_renderedNavigationCache[(navigation.Id, maxLevel)] = value;
return value;
}

private NavigationViewModel CreateNavigationModel(IRootNavigationItem<INavigationModel, INavigationItem> navigation) =>
private NavigationViewModel CreateNavigationModel(IRootNavigationItem<INavigationModel, INavigationItem> navigation, int maxLevel) =>
new()
{
Title = navigation.NavigationTitle,
Expand All @@ -36,6 +36,7 @@ private NavigationViewModel CreateNavigationModel(IRootNavigationItem<INavigatio
IsPrimaryNavEnabled = context.Configuration.Features.PrimaryNavEnabled,
IsUsingNavigationDropdown = context.Configuration.Features.PrimaryNavEnabled || navigation.IsUsingNavigationDropdown,
IsGlobalAssemblyBuild = false,
TopLevelItems = siteRoot.NavigationItems.OfType<INodeNavigationItem<INavigationModel, INavigationItem>>().ToList()
TopLevelItems = siteRoot.NavigationItems.OfType<INodeNavigationItem<INavigationModel, INavigationItem>>().ToList(),
MaxLevel = maxLevel
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public class NavigationTreeItem
public required bool IsPrimaryNavEnabled { get; init; }
public required bool IsGlobalAssemblyBuild { get; init; }
public required string RootNavigationId { get; set; }
// How many levels to render. Default is -1 (all levels)
public required int MaxLevel { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ public class NavigationViewModel

/// controls whether to split the navigation tree automatically
public required bool IsUsingNavigationDropdown { get; init; }

// How many levels to render. -1 means all
public required int MaxLevel { get; init; }
}
2 changes: 2 additions & 0 deletions src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@using Elastic.Documentation.Site.Navigation
@inherits RazorSlice<Elastic.Documentation.Site.Navigation.NavigationViewModel>

<div class="pb-20 font-body">
@{
var currentTopLevelItem = Model.TopLevelItems.FirstOrDefault(i => i.Id == Model.Tree.Id) ?? Model.Tree;
Expand Down Expand Up @@ -65,6 +66,7 @@
Level = 0,
SubTree = Model.Tree,
RootNavigationId = Model.Tree.Id,
MaxLevel = Model.MaxLevel
}))
</ul>
</div>
21 changes: 14 additions & 7 deletions src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,22 @@
</div>
@if (g.NavigationItems.Count > 0)
{
// Only render children if we're within the allowed level depth
// MaxLevel of -1 means render all levels
bool shouldRenderChildren = Model.MaxLevel == -1 || Model.Level < (Model.MaxLevel);
<ul class="w-full hidden peer-has-checked:block ml-4">
@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
@if (shouldRenderChildren)
{
IsPrimaryNavEnabled = Model.IsPrimaryNavEnabled,
IsGlobalAssemblyBuild = Model.IsGlobalAssemblyBuild,
Level = Model.Level + 1,
SubTree = g,
RootNavigationId = Model.RootNavigationId
}))
@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
{
IsPrimaryNavEnabled = Model.IsPrimaryNavEnabled,
IsGlobalAssemblyBuild = Model.IsGlobalAssemblyBuild,
Level = Model.Level + 1,
SubTree = g,
RootNavigationId = Model.RootNavigationId,
MaxLevel = Model.MaxLevel
}))
}
</ul>
}
</li>
Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/DocumentationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ private async Task GenerateDocumentationState(Cancel ctx)
await DocumentationSet.OutputDirectory.FileSystem.File.WriteAllBytesAsync(stateFile.FullName, bytes, ctx);
}

public async Task<string?> RenderLayout(MarkdownFile markdown, Cancel ctx)
public async Task<RenderResult> RenderLayout(MarkdownFile markdown, Cancel ctx)
{
await DocumentationSet.Tree.Resolve(ctx);
return await HtmlWriter.RenderLayout(markdown, ctx);
Expand Down
33 changes: 26 additions & 7 deletions src/Elastic.Markdown/HtmlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,21 @@ public string Render(string markdown, IFileInfo? source)
return MarkdownFile.CreateHtml(parsed);
}

public async Task<string> RenderLayout(MarkdownFile markdown, Cancel ctx = default)
public async Task<RenderResult> RenderLayout(MarkdownFile markdown, Cancel ctx = default)
{
var document = await markdown.ParseFullAsync(ctx);
return await RenderLayout(markdown, document, ctx);
}

private async Task<string> RenderLayout(MarkdownFile markdown, MarkdownDocument document, Cancel ctx = default)
private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDocument document, Cancel ctx = default)
{
var html = MarkdownFile.CreateHtml(document);
await DocumentationSet.Tree.Resolve(ctx);

var navigationHtml = await NavigationHtmlWriter.RenderNavigation(markdown.NavigationRoot, markdown.NavigationSource, ctx);
var fullNavigationHtml = await NavigationHtmlWriter.RenderNavigation(markdown.NavigationRoot, markdown.NavigationSource, INavigationHtmlWriter.AllLevels, ctx);
var miniNavigationHtml = DocumentationSet.Context.Configuration.Features.LazyLoadNavigation
? await NavigationHtmlWriter.RenderNavigation(markdown.NavigationRoot, markdown.NavigationSource, 1, ctx)
: "lazy navigation feature disabled";

var current = PositionalNavigation.GetCurrent(markdown);
var previous = PositionalNavigation.GetPrevious(markdown);
Expand Down Expand Up @@ -115,7 +118,7 @@ private async Task<string> RenderLayout(MarkdownFile markdown, MarkdownDocument
PreviousDocument = previous,
NextDocument = next,
Parents = parents,
NavigationHtml = navigationHtml,
NavigationHtml = DocumentationSet.Configuration.Features.LazyLoadNavigation ? miniNavigationHtml : fullNavigationHtml,
UrlPathPrefix = markdown.UrlPathPrefix,
AppliesTo = markdown.YamlFrontMatter?.AppliesTo,
GithubEditUrl = editUrl,
Expand All @@ -132,7 +135,12 @@ private async Task<string> RenderLayout(MarkdownFile markdown, MarkdownDocument
Products = allProducts,
VersionsConfig = DocumentationSet.Context.VersionsConfig
});
return await slice.RenderAsync(cancellationToken: ctx);
return new RenderResult
{
Html = await slice.RenderAsync(cancellationToken: ctx),
FullNavigationPartialHtml = fullNavigationHtml
};

}

public async Task<MarkdownDocument> WriteAsync(IFileInfo outputFile, MarkdownFile markdown, IConversionCollector? collector, Cancel ctx = default)
Expand Down Expand Up @@ -160,9 +168,20 @@ public async Task<MarkdownDocument> WriteAsync(IFileInfo outputFile, MarkdownFil
var document = await markdown.ParseFullAsync(ctx);

var rendered = await RenderLayout(markdown, document, ctx);
collector?.Collect(markdown, document, rendered);
await writeFileSystem.File.WriteAllTextAsync(path, rendered, ctx);
collector?.Collect(markdown, document, rendered.Html);
await writeFileSystem.File.WriteAllTextAsync(path, rendered.Html, ctx);

if (DocumentationSet.Configuration.Features.LazyLoadNavigation)
{
await writeFileSystem.File.WriteAllTextAsync(path.Replace(".html", ".nav.html"), rendered.FullNavigationPartialHtml, ctx);
}
return document;
}

}

public record RenderResult
{
public required string Html { get; init; }
public required string FullNavigationPartialHtml { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class GlobalNavigationHtmlWriter(
AssembleSources assembleSources
) : INavigationHtmlWriter
{
private readonly ConcurrentDictionary<Uri, string> _renderedNavigationCache = [];
private readonly ConcurrentDictionary<(Uri, int), string> _renderedNavigationCache = [];

private ImmutableHashSet<Uri> Phantoms { get; } = [.. navigationFile.Phantoms.Select(p => p.Source)];

Expand All @@ -44,7 +44,7 @@ private bool TryGetNavigationRoot(
return true;
}

public async Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, Cancel ctx = default)
public async Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, int maxLevel = -1, Cancel ctx = default)
{
if (Phantoms.Contains(navigationSource))
return string.Empty;
Expand All @@ -55,25 +55,25 @@ public async Task<string> RenderNavigation(IRootNavigationItem<INavigationModel,
if (Phantoms.Contains(navigationRootSource))
return string.Empty;

if (_renderedNavigationCache.TryGetValue(navigationRootSource, out var value))
if (_renderedNavigationCache.TryGetValue((navigationRootSource, maxLevel), out var value))
return value;

if (navigationRootSource == new Uri("docs-content:///"))
{
_renderedNavigationCache[navigationRootSource] = string.Empty;
_renderedNavigationCache[(navigationRootSource, maxLevel)] = string.Empty;
return string.Empty;
}

Console.WriteLine($"Rendering navigation for {navigationRootSource}");

var model = CreateNavigationModel(navigationRoot);
var model = CreateNavigationModel(navigationRoot, maxLevel);
value = await ((INavigationHtmlWriter)this).Render(model, ctx);
_renderedNavigationCache[navigationRootSource] = value;
_renderedNavigationCache[(navigationRootSource, maxLevel)] = value;

return value;
}

private NavigationViewModel CreateNavigationModel(DocumentationGroup group)
private NavigationViewModel CreateNavigationModel(DocumentationGroup group, int maxLevel)
{
var topLevelItems = globalNavigation.TopLevelItems;
return new NavigationViewModel
Expand All @@ -84,7 +84,8 @@ private NavigationViewModel CreateNavigationModel(DocumentationGroup group)
IsPrimaryNavEnabled = true,
IsUsingNavigationDropdown = true,
IsGlobalAssemblyBuild = true,
TopLevelItems = topLevelItems
TopLevelItems = topLevelItems,
MaxLevel = maxLevel
};
}
}
2 changes: 2 additions & 0 deletions src/tooling/docs-assembler/assembler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ environments:
content_source: current
google_tag_manager:
enabled: false
feature_flags:
LAZY_LOAD_NAVIGATION: true
dev:
uri: http://localhost:4000
content_source: next
Expand Down
12 changes: 9 additions & 3 deletions src/tooling/docs-builder/Http/DocumentationWebHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,16 @@ private async Task<IResult> ServeApiFile(ReloadableGeneratorState holder, string
private static async Task<IResult> ServeDocumentationFile(ReloadableGeneratorState holder, string slug, Cancel ctx)
{
var generator = holder.Generator;

const string navPartialSuffix = "index.nav.html";
var isNavPartial = slug.EndsWith(navPartialSuffix);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
slug = slug.Replace('/', Path.DirectorySeparatorChar);

if (isNavPartial)
slug = slug.Replace(navPartialSuffix, "index.md");

var s = Path.GetExtension(slug) == string.Empty ? Path.Combine(slug, "index.md") : slug;

if (!generator.DocumentationSet.FlatMappedFiles.TryGetValue(s, out var documentationFile))
{
s = Path.GetExtension(slug) == string.Empty ? slug + ".md" : s.Replace($"{Path.DirectorySeparatorChar}index.md", ".md");
Expand All @@ -202,7 +207,8 @@ private static async Task<IResult> ServeDocumentationFile(ReloadableGeneratorSta
{
case MarkdownFile markdown:
var rendered = await generator.RenderLayout(markdown, ctx);
return Results.Content(rendered, "text/html");
return Results.Content(isNavPartial ? rendered.FullNavigationPartialHtml : rendered.Html, "text/html");

case ImageFile image:
return Results.File(image.SourceFile.FullName, image.MimeType);
default:
Expand All @@ -216,7 +222,7 @@ private static async Task<IResult> ServeDocumentationFile(ReloadableGeneratorSta
return Results.NotFound();

var renderedNotFound = await generator.RenderLayout((notFoundDocumentationFile as MarkdownFile)!, ctx);
return Results.Content(renderedNotFound, "text/html", null, (int)HttpStatusCode.NotFound);
return Results.Content(renderedNotFound.Html, "text/html", null, (int)HttpStatusCode.NotFound);
}
}
}
Loading