Skip to content

Commit 4e9dcc9

Browse files
authored
PoC: Lazy loaded navigation (#1505)
* PoC: Lazy loaded navigation * Hide functionality behind the LAZY_LOAD_NAVIGATION feature flag * Refactor
1 parent 948f246 commit 4e9dcc9

File tree

14 files changed

+91
-35
lines changed

14 files changed

+91
-35
lines changed

src/Elastic.ApiExplorer/OpenApiGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ private async Task<IFileInfo> Render<T>(INavigationItem current, T page, ApiRend
288288
if (!outputFile.Directory!.Exists)
289289
outputFile.Directory.Create();
290290

291-
var navigationHtml = await navigationRenderer.RenderNavigation(current.NavigationRoot, new Uri("http://ignored.example"), ctx);
291+
var navigationHtml = await navigationRenderer.RenderNavigation(current.NavigationRoot, new Uri("http://ignored.example"), INavigationHtmlWriter.AllLevels, ctx);
292292
renderContext = renderContext with
293293
{
294294
CurrentNavigation = current,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public bool PrimaryNavEnabled
2020
set => _featureFlags["primary-nav"] = value;
2121
}
2222

23+
public bool LazyLoadNavigation
24+
{
25+
get => IsEnabled("LAZY_LOAD_NAVIGATION");
26+
set => _featureFlags["LAZY_LOAD_NAVIGATION"] = value;
27+
}
28+
2329
private bool IsEnabled(string key)
2430
{
2531
var envKey = $"FEATURE_{key.ToUpperInvariant().Replace('-', '_')}";

src/Elastic.Documentation.Site/Layout/_PagesNav.cshtml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
@inherits RazorSlice<Elastic.Documentation.Site.GlobalLayoutViewModel>
22
<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">
3+
4+
@if (Model.Features.LazyLoadNavigation)
5+
{
6+
<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>
7+
}
38
<nav
49
id="pages-nav"
510
class="sidebar-nav h-full">

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ namespace Elastic.Documentation.Site.Navigation;
88

99
public interface INavigationHtmlWriter
1010
{
11-
Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, Cancel ctx = default);
11+
const int AllLevels = -1;
12+
13+
Task<string> RenderNavigation(IRootNavigationItem<INavigationModel, INavigationItem> currentRootNavigation, Uri navigationSource, int maxLevel, Cancel ctx = default);
1214

1315
async Task<string> Render(NavigationViewModel model, Cancel ctx)
1416
{

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ namespace Elastic.Documentation.Site.Navigation;
1010
public class IsolatedBuildNavigationHtmlWriter(BuildContext context, IRootNavigationItem<INavigationModel, INavigationItem> siteRoot)
1111
: INavigationHtmlWriter
1212
{
13-
private readonly ConcurrentDictionary<string, string> _renderedNavigationCache = [];
13+
private readonly ConcurrentDictionary<(string, int), string> _renderedNavigationCache = [];
1414

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

21-
if (_renderedNavigationCache.TryGetValue(navigation.Id, out var value))
21+
if (_renderedNavigationCache.TryGetValue((navigation.Id, maxLevel), out var value))
2222
return value;
2323

24-
var model = CreateNavigationModel(navigation);
24+
var model = CreateNavigationModel(navigation, maxLevel);
2525
value = await ((INavigationHtmlWriter)this).Render(model, ctx);
26-
_renderedNavigationCache[navigation.Id] = value;
26+
_renderedNavigationCache[(navigation.Id, maxLevel)] = value;
2727
return value;
2828
}
2929

30-
private NavigationViewModel CreateNavigationModel(IRootNavigationItem<INavigationModel, INavigationItem> navigation) =>
30+
private NavigationViewModel CreateNavigationModel(IRootNavigationItem<INavigationModel, INavigationItem> navigation, int maxLevel) =>
3131
new()
3232
{
3333
Title = navigation.NavigationTitle,
@@ -36,6 +36,7 @@ private NavigationViewModel CreateNavigationModel(IRootNavigationItem<INavigatio
3636
IsPrimaryNavEnabled = context.Configuration.Features.PrimaryNavEnabled,
3737
IsUsingNavigationDropdown = context.Configuration.Features.PrimaryNavEnabled || navigation.IsUsingNavigationDropdown,
3838
IsGlobalAssemblyBuild = false,
39-
TopLevelItems = siteRoot.NavigationItems.OfType<INodeNavigationItem<INavigationModel, INavigationItem>>().ToList()
39+
TopLevelItems = siteRoot.NavigationItems.OfType<INodeNavigationItem<INavigationModel, INavigationItem>>().ToList(),
40+
MaxLevel = maxLevel
4041
};
4142
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ public class NavigationTreeItem
1212
public required bool IsPrimaryNavEnabled { get; init; }
1313
public required bool IsGlobalAssemblyBuild { get; init; }
1414
public required string RootNavigationId { get; set; }
15+
// How many levels to render. Default is -1 (all levels)
16+
public required int MaxLevel { get; init; }
1517
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ public class NavigationViewModel
1515

1616
/// controls whether to split the navigation tree automatically
1717
public required bool IsUsingNavigationDropdown { get; init; }
18+
19+
// How many levels to render. -1 means all
20+
public required int MaxLevel { get; init; }
1821
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@using Elastic.Documentation.Site.Navigation
22
@inherits RazorSlice<Elastic.Documentation.Site.Navigation.NavigationViewModel>
3+
34
<div class="pb-20 font-body">
45
@{
56
var currentTopLevelItem = Model.TopLevelItems.FirstOrDefault(i => i.Id == Model.Tree.Id) ?? Model.Tree;
@@ -65,6 +66,7 @@
6566
Level = 0,
6667
SubTree = Model.Tree,
6768
RootNavigationId = Model.Tree.Id,
69+
MaxLevel = Model.MaxLevel
6870
}))
6971
</ul>
7072
</div>

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,22 @@
5757
</div>
5858
@if (g.NavigationItems.Count > 0)
5959
{
60+
// Only render children if we're within the allowed level depth
61+
// MaxLevel of -1 means render all levels
62+
bool shouldRenderChildren = Model.MaxLevel == -1 || Model.Level < (Model.MaxLevel);
6063
<ul class="w-full hidden peer-has-checked:block ml-4">
61-
@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
64+
@if (shouldRenderChildren)
6265
{
63-
IsPrimaryNavEnabled = Model.IsPrimaryNavEnabled,
64-
IsGlobalAssemblyBuild = Model.IsGlobalAssemblyBuild,
65-
Level = Model.Level + 1,
66-
SubTree = g,
67-
RootNavigationId = Model.RootNavigationId
68-
}))
66+
@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
67+
{
68+
IsPrimaryNavEnabled = Model.IsPrimaryNavEnabled,
69+
IsGlobalAssemblyBuild = Model.IsGlobalAssemblyBuild,
70+
Level = Model.Level + 1,
71+
SubTree = g,
72+
RootNavigationId = Model.RootNavigationId,
73+
MaxLevel = Model.MaxLevel
74+
}))
75+
}
6976
</ul>
7077
}
7178
</li>

src/Elastic.Markdown/DocumentationGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ private async Task GenerateDocumentationState(Cancel ctx)
334334
await DocumentationSet.OutputDirectory.FileSystem.File.WriteAllBytesAsync(stateFile.FullName, bytes, ctx);
335335
}
336336

337-
public async Task<string?> RenderLayout(MarkdownFile markdown, Cancel ctx)
337+
public async Task<RenderResult> RenderLayout(MarkdownFile markdown, Cancel ctx)
338338
{
339339
await DocumentationSet.Tree.Resolve(ctx);
340340
return await HtmlWriter.RenderLayout(markdown, ctx);

0 commit comments

Comments
 (0)