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
59 changes: 44 additions & 15 deletions src/Elastic.Markdown/Assets/pages-nav.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,62 @@
import {$, $$} from "select-dom";

type NavExpandState = { [key: string]: boolean };
type NavExpandState = {
current:string,
selected: Record<string, boolean>
};
const PAGE_NAV_EXPAND_STATE_KEY = 'pagesNavState';
const navState = JSON.parse(sessionStorage.getItem(PAGE_NAV_EXPAND_STATE_KEY) ?? "{}") as NavExpandState

// Initialize the nav state from the session storage
// Return a function to keep the nav state in the session storage that should be called before the page is unloaded
function keepNavState(nav: HTMLElement): () => void {
const inputs = $$('input[type="checkbox"]', nav);
if (navState) {
inputs.forEach(input => {
const key = input.id;
if ('shouldExpand' in input.dataset && input.dataset['shouldExpand'] === 'true') {

const currentNavigation = nav.dataset.currentNavigation;
const currentPageId = nav.dataset.currentPageId;

let navState = JSON.parse(sessionStorage.getItem(PAGE_NAV_EXPAND_STATE_KEY) ?? "{}") as NavExpandState
if (navState.current !== currentNavigation)
{
sessionStorage.removeItem(PAGE_NAV_EXPAND_STATE_KEY);
navState = { current: currentNavigation } as NavExpandState;
}
if (currentPageId)
{
const currentPageLink = $('a[id="page-' + currentPageId + '"]', nav);
currentPageLink.classList.add('current');
currentPageLink.classList.add('pointer-events-none');
currentPageLink.classList.add('text-blue-elastic!');
currentPageLink.classList.add('font-semibold');

const parentIds = nav.dataset.currentPageParentIds?.split(',') ?? [];
for (const parentId of parentIds)
{
const input = $('input[type="checkbox"][id=\"'+parentId+'\"]', nav) as HTMLInputElement;
if (input) {
input.checked = true;
} else {
if (key in navState) {
input.checked = navState[key];
}
const link = input.nextElementSibling as HTMLAnchorElement;
link.classList.add('font-semibold');
}
});
}
}

// expand items previously selected
for (const groupId in navState.selected)
{
const input = $('input[type="checkbox"][id=\"'+groupId+'\"]', nav) as HTMLInputElement;
input.checked = navState.selected[groupId];
}

return () => {
const inputs = $$('input[type="checkbox"]', nav);
const state: NavExpandState = inputs.reduce((state: NavExpandState, input) => {
// store all expanded groups
const inputs = $$('input[type="checkbox"]:checked', nav);
const selectedMap: Record<string, boolean> = inputs
.filter(input => input.checked)
.reduce((state: Record<string, boolean>, input) => {
const key = input.id;
const value = input.checked;
return { ...state, [key]: value};
}, {});
const state = { current: currentNavigation, selected: selectedMap };
sessionStorage.setItem(PAGE_NAV_EXPAND_STATE_KEY, JSON.stringify(state));
}
}
Expand Down
20 changes: 18 additions & 2 deletions src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using YamlDotNet.Serialization;

namespace Elastic.Markdown.IO;

Expand All @@ -32,6 +31,8 @@ public MarkdownFile(IFileInfo sourceFile, IDirectoryInfo rootPath, MarkdownParse
Collector = context.Collector;
}

public string Id { get; } = Guid.NewGuid().ToString("N")[..8];

private DiagnosticsCollector Collector { get; }

public DocumentationGroup? Parent
Expand Down Expand Up @@ -76,10 +77,11 @@ public string? NavigationTitle

public int NavigationIndex { get; internal set; } = -1;

public string? GroupId { get; set; }

private bool _instructionsParsed;
private DocumentationGroup? _parent;
private string? _title;

public MarkdownFile[] YieldParents()
{
var parents = new List<MarkdownFile>();
Expand All @@ -92,6 +94,20 @@ public MarkdownFile[] YieldParents()
} while (parent != null);
return parents.ToArray();
}
public string[] YieldParentGroups()
{
var parents = new List<string>();
if (GroupId is not null)
parents.Add(GroupId);
var parent = Parent;
do
{
if (parent is not null)
parents.Add(parent.Id);
parent = parent?.Parent;
} while (parent != null);
return [.. parents];
}

public async Task<MarkdownDocument> MinimalParseAsync(Cancel ctx)
{
Expand Down
18 changes: 16 additions & 2 deletions src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using Elastic.Markdown.Diagnostics;
using Elastic.Markdown.Helpers;
using Elastic.Markdown.IO.Configuration;

namespace Elastic.Markdown.IO.Navigation;
Expand All @@ -11,14 +12,24 @@ public interface INavigationItem
{
int Order { get; }
int Depth { get; }
string Id { get; }
}

public record GroupNavigation(int Order, int Depth, DocumentationGroup Group) : INavigationItem;
public record FileNavigation(int Order, int Depth, MarkdownFile File) : INavigationItem;
public record GroupNavigation(int Order, int Depth, DocumentationGroup Group) : INavigationItem
{
public string Id { get; } = Group.Id;
}

public record FileNavigation(int Order, int Depth, MarkdownFile File) : INavigationItem
{
public string Id { get; } = File.Id;
}


public class DocumentationGroup
{
public string Id { get; } = Guid.NewGuid().ToString("N")[..8];

public MarkdownFile? Index { get; set; }

private IReadOnlyCollection<MarkdownFile> FilesInOrder { get; }
Expand Down Expand Up @@ -51,6 +62,9 @@ public DocumentationGroup(
{
Depth = depth;
Index = ProcessTocItems(context, index, toc, lookup, folderLookup, depth, ref fileIndex, out var groups, out var files, out var navigationItems);
if (Index is not null)
Index.GroupId = Id;


GroupsInOrder = groups;
FilesInOrder = files;
Expand Down
6 changes: 4 additions & 2 deletions src/Elastic.Markdown/Slices/HtmlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ private async Task<string> RenderNavigation(MarkdownFile markdown, Cancel ctx =
return await slice.RenderAsync(cancellationToken: ctx);
}

private string? _renderedNavigation;

public async Task<string> RenderLayout(MarkdownFile markdown, Cancel ctx = default)
{
var document = await markdown.ParseFullAsync(ctx);
var html = markdown.CreateHtml(document);
await DocumentationSet.Tree.Resolve(ctx);
var navigationHtml = await RenderNavigation(markdown, ctx);
_renderedNavigation ??= await RenderNavigation(markdown, ctx);

var previous = DocumentationSet.GetPrevious(markdown);
var next = DocumentationSet.GetNext(markdown);
Expand All @@ -66,7 +68,7 @@ public async Task<string> RenderLayout(MarkdownFile markdown, Cancel ctx = defau
CurrentDocument = markdown,
PreviousDocument = previous,
NextDocument = next,
NavigationHtml = navigationHtml,
NavigationHtml = _renderedNavigation,
UrlPathPrefix = markdown.UrlPathPrefix,
Applies = markdown.YamlFrontMatter?.AppliesTo,
GithubEditUrl = editUrl,
Expand Down
7 changes: 6 additions & 1 deletion src/Elastic.Markdown/Slices/Layout/_PagesNav.cshtml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
@inherits RazorSlice<LayoutViewModel>
<aside class="sidebar hidden lg:block order-1 w-100 border-r-1 border-r-gray-200">
<nav id="pages-nav" class="sidebar-nav pr-6">
<nav id="pages-nav" class="sidebar-nav pr-6"
data-current-page-id="@Model.CurrentDocument.Id"
data-current-page-parent-ids="@(string.Join(",",Model.ParentIds))"
@* used to invalidate session storage *@
data-current-navigation="@LayoutViewModel.CurrentNavigationId">

@(new HtmlString(Model.NavigationHtml))
</nav>
<script src="@Model.Static("pages-nav.js")"></script>
Expand Down
27 changes: 12 additions & 15 deletions src/Elastic.Markdown/Slices/Layout/_TocTreeNav.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
@inherits RazorSlice<NavigationTreeItem>
@foreach (var item in Model.SubTree.NavigationItems)
{
var id = item.Id;
if (item is FileNavigation file)
{
var f = file.File;
var isCurrent = f == Model.CurrentDocument;
<li class="block ml-2 pl-2 border-l-1 border-l-gray-200 group/li @(isCurrent ? "current" : string.Empty)">
<li class="block ml-2 pl-2 border-l-1 border-l-gray-200 group/li">
<div class="flex">
<a
class="sidebar-link my-1 ml-5 group-[.current]/li:text-blue-elastic! @(isCurrent ? "pointer-events-none" : string.Empty)"
href="@f.Url"
@(isCurrent ? "aria-current=page" : string.Empty)>
class="sidebar-link my-1 ml-5 group-[.current]/li:text-blue-elastic!"
id="page-@id"
href="@f.Url">
@f.NavigationTitle
</a>
</div>
Expand All @@ -21,13 +21,10 @@
else if (item is GroupNavigation folder)
{
var g = folder.Group;
var isCurrent = g.Index == Model.CurrentDocument;
const int initialExpandLevel = 1;
var containsCurrent = g.HoldsCurrent(Model.CurrentDocument) || g.ContainsCurrentPage(Model.CurrentDocument);
var shouldInitiallyExpand = containsCurrent || g.Depth <= initialExpandLevel;
var uuid = Guid.NewGuid().ToString();
<li class="flex flex-wrap @(g.Depth > 1 ? "ml-2 pl-2 border-l-1 border-l-gray-200" : string.Empty)">
<label for="@uuid" class="peer group/label flex items-center overflow-hidden @(g.Depth == 1 ? "mt-2" : "")">
var shouldInitiallyExpand = g.Depth <= initialExpandLevel;
<li class="flex flex-wrap group-navigation @(g.Depth > 1 ? "ml-2 pl-2 border-l-1 border-l-gray-200" : string.Empty)">
<label for="@id" class="peer group/label flex items-center overflow-hidden @(g.Depth == 1 ? "mt-2" : "")">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
Expand All @@ -38,22 +35,22 @@
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>
</svg>
<input
id="@uuid"
id="@id"
type="checkbox"
class="hidden"
aria-hidden="true"
data-should-expand="@((containsCurrent).ToLowerString())"
@(shouldInitiallyExpand ? "checked" : string.Empty)
>
<a
href="@g.Index?.Url"
class="sidebar-link inline-block my-1 @(g.Depth == 1 ? "uppercase tracking-[0.05em] text-ink-light font-semibold" : string.Empty) @(containsCurrent ? "font-semibold" : string.Empty) @(isCurrent ? "current pointer-events-none text-blue-elastic!" : string.Empty)">
id="page-@g.Index?.Id"
class="sidebar-link inline-block my-1 @(g.Depth == 1 ? "uppercase tracking-[0.05em] text-ink-light font-semibold" : string.Empty)">
@g.Index?.NavigationTitle
</a>
</label>
@if (g.NavigationItems.Count > 0)
{
<ul class="h-0 w-full overflow-y-hidden peer-has-checked:h-auto peer-has-[:focus]:h-auto has-[:focus]:h-auto peer-has-checked:my-1" data-has-current="@g.ContainsCurrentPage(Model.CurrentDocument)">
<ul class="h-0 w-full overflow-y-hidden peer-has-checked:h-auto peer-has-[:focus]:h-auto has-[:focus]:h-auto peer-has-checked:my-1">
@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
{
Level = g.Depth,
Expand Down
5 changes: 5 additions & 0 deletions src/Elastic.Markdown/Slices/_ViewModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ public class IndexViewModel

public class LayoutViewModel
{
/// Used to identify the navigation for the current compilation
/// We want to reset users sessionStorage every time this changes to invalidate
/// the guids that no longer exist
public static string CurrentNavigationId { get; } = Guid.NewGuid().ToString("N")[..8];
public string Title { get; set; } = "Elastic Documentation";
public string RawTitle { get; set; } = "Elastic Documentation";
public required IReadOnlyCollection<PageTocItem> PageTocItems { get; init; }
public required DocumentationGroup Tree { get; init; }
public string[] ParentIds => [.. CurrentDocument.YieldParentGroups()];
public required MarkdownFile CurrentDocument { get; init; }
public required MarkdownFile? Previous { get; init; }
public required MarkdownFile? Next { get; init; }
Expand Down
Loading