Skip to content

Commit aa8f418

Browse files
authored
Add pages navigation (#447)
* Add pages navigation * ok * Enhance nav * Fine tuning * style * Revert to sessionStorage * Lighter gray * Enhance * Refactor and fix * cleanup * fix spacing * Split into multiple views
1 parent e94971e commit aa8f418

25 files changed

+406
-123
lines changed

docs/docset.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,11 @@ toc:
105105
children:
106106
- file: first-page.md
107107
- file: second-page.md
108+
- folder: deeply-nested
109+
children:
110+
- file: index.md
111+
- file: foo.md
112+
- file: bar.md
113+
- folder: baz
114+
children:
115+
- file: qux.md

docs/testing/deeply-nested/bar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Bar

docs/testing/deeply-nested/baz/qux.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Qux

docs/testing/deeply-nested/foo.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Foo

docs/testing/deeply-nested/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Deeply Nested

src/Elastic.Markdown/Assets/fonts.css

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
1-
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
1+
@font-face {
2+
font-family: "Inter";
3+
src: url("./fonts/InterVariable.woff2") format("woff2");
4+
font-display: swap;
5+
}
26

7+
@font-face {
8+
font-family: "Mier B";
9+
src: url("./fonts/MierB-Regular.woff2") format("woff2");
10+
font-weight: normal;
11+
font-display: swap;
12+
}
13+
14+
@font-face {
15+
font-family: "Mier B";
16+
src: url("./fonts/MierB-Bold.woff2") format("woff2");
17+
font-weight: bold;
18+
font-display: swap;
19+
}
320

421
@font-face {
522
font-family: "Mier B";
6-
src: url("./fonts/MierB-Regular.woff2") format("woff2")
23+
src: url("./fonts/MierB-Demi.woff2") format("woff2");
24+
font-weight: 600;
25+
font-display: swap;
726
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

src/Elastic.Markdown/Assets/hljs.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {mergeHTMLPlugin} from "./hljs-merge-html-plugin";
2+
import hljs from "highlight.js";
3+
4+
hljs.registerLanguage('apiheader', function() {
5+
return {
6+
case_insensitive: true, // language is case-insensitive
7+
keywords: 'GET POST PUT DELETE HEAD OPTIONS PATCH',
8+
contains: [
9+
hljs.HASH_COMMENT_MODE,
10+
{
11+
className: "subst", // (pathname: path1/path2/dothis) color #ab5656
12+
begin: /(?<=(?:\/|GET |POST |PUT |DELETE |HEAD |OPTIONS |PATH))[^?\n\r\/]+/,
13+
}
14+
], }
15+
})
16+
17+
hljs.addPlugin(mergeHTMLPlugin);
18+
19+
export function initHighlight() {
20+
hljs.highlightAll();
21+
}

src/Elastic.Markdown/Assets/main.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,5 @@
1-
import hljs from "highlight.js";
2-
import {mergeHTMLPlugin} from "./hljs-merge-html-plugin";
1+
import {initNav} from "./pages-nav";
2+
import {initHighlight} from "./hljs";
33

4-
hljs.registerLanguage('apiheader', function() {
5-
return {
6-
case_insensitive: true, // language is case-insensitive
7-
keywords: 'GET POST PUT DELETE HEAD OPTIONS PATCH',
8-
contains: [
9-
hljs.HASH_COMMENT_MODE,
10-
{
11-
className: "subst", // (pathname: path1/path2/dothis) color #ab5656
12-
begin: /(?<=(?:\/|GET |POST |PUT |DELETE |HEAD |OPTIONS |PATH))[^?\n\r\/]+/,
13-
}
14-
], }
15-
})
16-
17-
hljs.addPlugin(mergeHTMLPlugin);
18-
hljs.highlightAll();
4+
initNav();
5+
initHighlight();
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {$, $$} from "select-dom/strict";
2+
3+
type NavExpandState = { [key: string]: boolean };
4+
const PAGE_NAV_EXPAND_STATE_KEY = 'pagesNavState';
5+
const navState = JSON.parse(sessionStorage.getItem(PAGE_NAV_EXPAND_STATE_KEY)) as NavExpandState
6+
7+
// Initialize the nav state from the session storage
8+
// Return a function to keep the nav state in the session storage that should be called before the page is unloaded
9+
function keepNavState(nav: HTMLElement): () => void {
10+
const inputs = $$('input[type="checkbox"]', nav);
11+
if (navState) {
12+
inputs.forEach(input => {
13+
const key = input.id;
14+
if ('shouldExpand' in input.dataset && input.dataset['shouldExpand'] === 'true') {
15+
input.checked = true;
16+
} else {
17+
input.checked = navState[key];
18+
}
19+
});
20+
}
21+
22+
return () => {
23+
const inputs = $$('input[type="checkbox"]', nav);
24+
const state: NavExpandState = inputs.reduce((state: NavExpandState, input) => {
25+
const key = input.id;
26+
const value = input.checked;
27+
return { ...state, [key]: value};
28+
}, {});
29+
sessionStorage.setItem(PAGE_NAV_EXPAND_STATE_KEY, JSON.stringify(state));
30+
}
31+
}
32+
33+
type NavScrollPosition = number;
34+
const PAGE_NAV_SCROLL_POSITION_KEY = 'pagesNavScrollPosition';
35+
const pagesNavScrollPosition: NavScrollPosition = parseInt(
36+
sessionStorage.getItem(PAGE_NAV_SCROLL_POSITION_KEY) ?? '0'
37+
);
38+
39+
40+
// Initialize the nav scroll position from the session storage
41+
// Return a function to keep the nav scroll position in the session storage that should be called before the page is unloaded
42+
function keepNavPosition(nav: HTMLElement): () => void {
43+
if (pagesNavScrollPosition) {
44+
nav.scrollTop = pagesNavScrollPosition;
45+
}
46+
return () => {
47+
sessionStorage.setItem(PAGE_NAV_SCROLL_POSITION_KEY, nav.scrollTop.toString());
48+
}
49+
}
50+
51+
function scrollCurrentNaviItemIntoView(nav: HTMLElement, delay: number) {
52+
setTimeout(() => {
53+
const currentNavItem = $('.current', nav);
54+
if (currentNavItem && !isElementInViewport(currentNavItem)) {
55+
currentNavItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
56+
}
57+
}, delay);
58+
}
59+
function isElementInViewport(el: HTMLElement): boolean {
60+
const rect = el.getBoundingClientRect();
61+
return (
62+
rect.top >= 0 &&
63+
rect.left >= 0 &&
64+
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
65+
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
66+
);
67+
}
68+
69+
export function initNav() {
70+
const pagesNav = $('#pages-nav');
71+
const keepNavStateCallback = keepNavState(pagesNav);
72+
const keepNavPositionCallback = keepNavPosition(pagesNav);
73+
scrollCurrentNaviItemIntoView(pagesNav, 100);
74+
window.addEventListener('beforeunload', () => {
75+
keepNavStateCallback();
76+
keepNavPositionCallback();
77+
}, true);
78+
}

src/Elastic.Markdown/Assets/plugins.css

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
@import "tailwindcss";
22
@import "./fonts.css";
3-
@import "./plugins.css";
43
@import "./theme.css";
54
@import "highlight.js/styles/atom-one-dark.css";
65
@import "./markdown/typography.css";
76

87
#default-search::-webkit-search-cancel-button {
9-
@apply pr-2;
8+
padding-right: calc(var(--spacing) * 2);
109
-webkit-appearance: none;
1110
height: 16px;
1211
width: 16px;
@@ -15,3 +14,35 @@
1514
cursor: pointer;
1615
background-repeat: no-repeat;
1716
}
17+
18+
#pages-nav {
19+
&::-webkit-scrollbar-track {
20+
background-color: transparent;
21+
}
22+
&:hover::-webkit-scrollbar-thumb {
23+
background-color: var(--color-gray-light);
24+
}
25+
&::-webkit-scrollbar {
26+
width: calc(var(--spacing) * 2);
27+
height: calc(var(--spacing) * 2);
28+
}
29+
&::-webkit-scrollbar-thumb {
30+
border-radius: var(--spacing);
31+
}
32+
33+
scrollbar-gutter: stable;
34+
}
35+
36+
37+
#pages-nav li.current {
38+
position: relative;
39+
&::before {
40+
content: "";
41+
position: absolute;
42+
top: 50%;
43+
left: -1px;
44+
width: calc(var(--spacing) * 6);
45+
height: 1px;
46+
background-color: var(--color-gray-200);
47+
}
48+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Elastic.Markdown.Helpers;
6+
7+
public static class BoolExtensions
8+
{
9+
public static string ToLowerString(this bool @bool) => @bool.ToString().ToLowerInvariant();
10+
}

src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ public class DocumentationGroup
3333

3434
public int Depth { get; }
3535

36+
public bool ContainsCurrentPage(MarkdownFile current) => NavigationItems.Any(n => n switch
37+
{
38+
FileNavigation f => f.File == current,
39+
GroupNavigation g => g.Group.ContainsCurrentPage(current),
40+
_ => false
41+
});
42+
3643
public DocumentationGroup(
3744
BuildContext context,
3845
IReadOnlyCollection<ITocItem> toc,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@inherits RazorSlice<LayoutViewModel>
2+
<ol class="flex-1 mb-6" itemscope="" itemtype="https://schema.org/BreadcrumbList">
3+
<li class="inline text-ink text-sm hover:text-ink leading-[1.2em] tracking-[-0.02em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
4+
<a itemprop="item" href="@Model.UrlPathPrefix/">
5+
<span itemprop="name" class="hover:text-ink">Elastic</span>
6+
</a>
7+
<meta itemprop="position" content="1">
8+
</li>
9+
@foreach (var item in Model.Parents.Reverse().Skip(1))
10+
{
11+
<li class="inline text-gray-500 text-sm leading-[1.2em] tracking-[-0.02em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
12+
<span class="px-1">/</span>
13+
<a itemprop="item" href="@item.Url">
14+
<span itemprop="name" class="hover:text-ink">@item.NavigationTitle</span>
15+
</a>
16+
<meta itemprop="position" content="2">
17+
</li>
18+
}
19+
</ol>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@inherits RazorSlice<LayoutViewModel>
2+
<header class="sticky top-0 bg-blue-developer max-w-screen px-6 pb-6 flex items-center justify-center">
3+
<div class="container flex flex-wrap lg:flex-nowrap">
4+
<div class="h-10 mt-6 basis-full lg:basis-auto">
5+
<a href="@Model.UrlPathPrefix/">
6+
<img src="@Model.Static("logo-elastic-horizontal-color-reverse.svg")" alt="Elastic" class="h-10">
7+
</a>
8+
</div>
9+
<form role="search" class="hidden lg:block grow basis-full lg:basis-auto shrink-0 mt-6 lg:mx-10 h-10" autocomplete="off">
10+
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label>
11+
<div class="relative h-10">
12+
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none ml-2">
13+
<svg class="w-4 h-4 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
14+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
15+
</svg>
16+
</div>
17+
<div class="rounded-full bg-linear-65 from-pink/[.4] to-teal/[.4] p-[2px] h-10">
18+
<input type="search" id="default-search" class="flex items-center h-full w-full p-2 ps-10 text-center bg-[#081335] rounded-full focus-visible:outline-[blue]/[.3] focus-visible:outline-2 truncate text-ellipsis text-white" placeholder="Try searching a document here..." required/>
19+
</div>
20+
</div>
21+
</form>
22+
<div class="flex grow lg:flex-none h-10 mt-6">
23+
<a href="https://cloud.elastic.co/registration" class="grow select-none cursor-pointer text-white text-nowrap bg-blue-elastic hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-semibold rounded-sm px-6 py-2.5 me-3 focus:outline-none h-10 flex items-center justify-center">Start free trial</a>
24+
<a href="https://elastic.co/contact" class="grow cursor-pointer text-white text-nowrap border-2 border-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-semibold rounded-sm px-6 py-2.5 text-center h-10 flex items-center justify-center">Contact Sales</a>
25+
</div>
26+
</div>
27+
</header>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@inherits RazorSlice<LayoutViewModel>
2+
<aside class="hidden lg:block">
3+
<nav id="pages-nav" class="sticky top-22 w-80 z-10 max-h-[calc(100vh-var(--spacing)*22)] overflow-y-auto">
4+
@(new HtmlString(Model.NavigationHtml))
5+
</nav>
6+
</aside>
Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
@inherits RazorSlice<NavigationViewModel>
2-
<aside id="lside" class="sy-lside md:w-72 md:shrink-0 print:hidden">
3-
<div class="sy-lside-inner md:sticky">
4-
<div class="sy-scrollbar p-6">
5-
<div class="globaltoc" data-expand-depth="0">
6-
<p class="caption" role="heading" aria-level="3">
7-
<span class="caption-text">Elastic Docs Guide</span>
8-
</p>
9-
<ul class="current">@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
10-
{
11-
Level = Model.Tree.Depth,
12-
SubTree = Model.Tree,
13-
CurrentDocument = Model.CurrentDocument
14-
}))
15-
</ul>
2+
3+
@if (Model.IsRedesign)
4+
{
5+
<div class="pt-6 pb-20">
6+
<ul class="block w-full">
7+
@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
8+
{
9+
Level = Model.Tree.Depth,
10+
SubTree = Model.Tree,
11+
CurrentDocument = Model.CurrentDocument
12+
}))
13+
</ul>
14+
</div>
15+
}
16+
else
17+
{
18+
<aside id="lside" class="sy-lside md:w-72 md:shrink-0 print:hidden">
19+
<div class="sy-lside-inner md:sticky">
20+
<div class="sy-scrollbar p-6">
21+
<div class="globaltoc" data-expand-depth="0">
22+
<p class="caption" role="heading" aria-level="3">
23+
<span class="caption-text">Elastic Docs Guide</span>
24+
</p>
25+
<ul class="current">@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
26+
{
27+
Level = Model.Tree.Depth,
28+
SubTree = Model.Tree,
29+
CurrentDocument = Model.CurrentDocument
30+
}))
31+
</ul>
32+
</div>
1633
</div>
1734
</div>
18-
</div>
19-
</aside>
20-
<div class="lside-overlay js-menu" role="button" aria-label="Close left sidebar" aria-controls="lside" aria-expanded="false"></div>
35+
</aside>
36+
<div class="lside-overlay js-menu" role="button" aria-label="Close left sidebar" aria-controls="lside" aria-expanded="false"></div>
37+
}

0 commit comments

Comments
 (0)