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
3 changes: 3 additions & 0 deletions src/Elastic.Markdown/Assets/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {initNav} from "./pages-nav";
import {initTocNav} from "./toc-nav";

import {initHighlight} from "./hljs";

initNav();
initTocNav();
initHighlight();
25 changes: 25 additions & 0 deletions src/Elastic.Markdown/Assets/markdown/list.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#elastic-docs-v3 {
ol,ul {
font-family: "Inter", sans-serif;
@apply text-base text-body mb-6;
line-height: 1.5em;
letter-spacing: 0;
margin-left: 1.5em;
}

ol {
list-style-type: decimal;
}

ul {
list-style-type: disc;
}

li {
margin-bottom: calc(var(--spacing) * 3);

p {
margin-bottom: 0;
}
}
}
11 changes: 6 additions & 5 deletions src/Elastic.Markdown/Assets/markdown/typography.css
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
#elastic-docs-v3 {

h1 {
font-family: "Mier B", "Inter", sans-serif;
@apply text-4xl text-ink font-bold mb-6;
@apply text-4xl text-black mb-6 mt-4;
line-height: 1.2em;
letter-spacing: -0.04em;
}

h2 {
font-family: "Mier B", "Inter", sans-serif;
@apply text-2xl text-ink mb-6;
@apply text-2xl text-black mb-6 mt-4;
line-height: 1.2em;
letter-spacing: -0.02em;
}

h3 {
font-family: "Mier B", "Inter", sans-serif;
@apply text-xl text-ink font-bold mb-6;
@apply text-xl text-black font-bold mb-6 mt-4;
line-height: 1.2em;
letter-spacing: -0.02em;
}

p {
font-family: "Inter", sans-serif;
@apply text-base text-body mb-6;
@apply text-base text-ink text-body mb-6;
line-height: 1.5em;
letter-spacing: 0;
}

a {
font-family: "Inter", sans-serif;
@apply text-blue-elastic hover:underline underline-offset-4;
@apply text-blue-elastic underline hover:text-blue-800;
}
}
11 changes: 8 additions & 3 deletions src/Elastic.Markdown/Assets/pages-nav.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {$, $$} from "select-dom/strict";
import {$, $$} from "select-dom";

type NavExpandState = { [key: string]: boolean };
const PAGE_NAV_EXPAND_STATE_KEY = 'pagesNavState';
const navState = JSON.parse(sessionStorage.getItem(PAGE_NAV_EXPAND_STATE_KEY)) as NavExpandState
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
Expand All @@ -14,7 +14,9 @@ function keepNavState(nav: HTMLElement): () => void {
if ('shouldExpand' in input.dataset && input.dataset['shouldExpand'] === 'true') {
input.checked = true;
} else {
input.checked = navState[key];
if (key in navState) {
input.checked = navState[key];
}
}
});
}
Expand Down Expand Up @@ -68,6 +70,9 @@ function isElementInViewport(el: HTMLElement): boolean {

export function initNav() {
const pagesNav = $('#pages-nav');
if (!pagesNav) {
return;
}
const keepNavStateCallback = keepNavState(pagesNav);
const keepNavPositionCallback = keepNavPosition(pagesNav);
scrollCurrentNaviItemIntoView(pagesNav, 100);
Expand Down
76 changes: 57 additions & 19 deletions src/Elastic.Markdown/Assets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import "./theme.css";
@import "highlight.js/styles/atom-one-dark.css";
@import "./markdown/typography.css";
@import "./markdown/list.css";

#default-search::-webkit-search-cancel-button {
padding-right: calc(var(--spacing) * 2);
Expand All @@ -15,25 +16,6 @@
background-repeat: no-repeat;
}

#pages-nav {
&::-webkit-scrollbar-track {
background-color: transparent;
}
&:hover::-webkit-scrollbar-thumb {
background-color: var(--color-gray-light);
}
&::-webkit-scrollbar {
width: calc(var(--spacing) * 2);
height: calc(var(--spacing) * 2);
}
&::-webkit-scrollbar-thumb {
border-radius: var(--spacing);
}

scrollbar-gutter: stable;
}


#pages-nav li.current {
position: relative;
&::before {
Expand All @@ -46,3 +28,59 @@
background-color: var(--color-gray-200);
}
}

#toc-nav a.current {
color: var(--color-blue-elastic);
&:hover {
color: var(--color-blue-elastic);
}
}

@layer components {
.link {
font-family: "Mier B", "Inter", sans-serif;
@apply
text-blue-elastic
text-nowrap
font-semibold
hover:text-blue-800
inline-flex
justify-center
items-center;

.link-arrow {
@apply
shrink-0
size-7
ml-2
transition-transform
ease-out;
}

&:hover{
svg {
@apply translate-x-2;
}
}
}

.sidebar {
.sidebar-nav {
@apply sticky top-22 z-30 overflow-y-auto;
max-height: calc(100vh - var(--spacing) * 22);
}

.sidebar-link {
@apply
text-ink-light
hover:text-black
text-sm
leading-[1.2em]
tracking-[-0.02em];
}
}
}

* {
scroll-margin-top: calc(var(--spacing) * 26);
}
138 changes: 138 additions & 0 deletions src/Elastic.Markdown/Assets/toc-nav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { $$, $ } from 'select-dom';

interface TocElements {
headings: Element[];
tocLinks: HTMLAnchorElement[];
tocContainer: HTMLUListElement | null;
progressIndicator: HTMLDivElement;
}

// 34 is the height of the header + some padding
// 4 is the base spacing unit
const HEADING_OFFSET = 34 * 4;

function initializeTocElements(): TocElements {
const headings = $$('h2, h3');
const tocLinks = $$('#toc-nav li>a') as HTMLAnchorElement[];
const tocContainer = $('#toc-nav ul') as HTMLUListElement;
const progressIndicator = $('.toc-progress-indicator', tocContainer) as HTMLDivElement;
return { headings, tocLinks, tocContainer,progressIndicator };
}

// Find the current TOC links based on visible headings
// It can return multiple links because headings in a tab can have the same position
function findCurrentTocLinks(elements: TocElements): HTMLAnchorElement[] {
let currentTocLinks: HTMLAnchorElement[] = [];
let currentTop: number | null = null;
for (const heading of elements.headings) {
const rect = heading.getBoundingClientRect();
if (rect.top <= HEADING_OFFSET) {
if (currentTop !== null && Math.abs(rect.top - currentTop) > 1) {
currentTocLinks = [];
}
currentTop = rect.top;
const foundLink = elements.tocLinks.find(link =>
link.getAttribute('href') === `#${heading.closest('section')?.id}`
);
if (foundLink) {
currentTocLinks.push(foundLink);
}
}
}
return currentTocLinks;
}

// Get visible headings in viewport
function getVisibleHeadings(elements: TocElements) {
return elements.headings.filter(heading => {
const rect = heading.getBoundingClientRect();
return rect.top - HEADING_OFFSET + 64 >= 0 && rect.top <= window.innerHeight;
});
}

// If the user has scrolled to the bottom of the page,
// and there are still multiple headings visible, we need to
// handle the progress indicator differently.
// In this case it sets the indicator for all visible headings.
function handleBottomScroll(elements: TocElements) {
const visibleHeadings = getVisibleHeadings(elements);
if (visibleHeadings.length === 0) return;
const firstHeading = visibleHeadings[0];
const lastHeading = visibleHeadings[visibleHeadings.length - 1];
const firstLink = elements.tocLinks.find(link =>
link.getAttribute('href') === `#${firstHeading.parentElement?.id}`
)?.closest('li');
const lastLink = elements.tocLinks.find(link =>
link.getAttribute('href') === `#${lastHeading.parentElement?.id}`
)?.closest('li');
if (firstLink && lastLink && elements.tocContainer) {
const tocRect = elements.tocContainer.getBoundingClientRect();
const firstRect = firstLink.getBoundingClientRect();
const lastRect = lastLink.getBoundingClientRect();
updateProgressIndicatorPosition(
elements.progressIndicator,
firstRect.top - tocRect.top,
(lastRect.top + lastRect.height) - firstRect.top
);
}
}

function updateProgressIndicatorPosition(
indicator: HTMLDivElement,
top: number,
height: number
) {
indicator.style.top = `${top}px`;
indicator.style.height = `${height}px`;
}

function updateIndicator(elements: TocElements) {
if (!elements.tocContainer) return;

const isAtBottom = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 10;
const currentTocLinks = findCurrentTocLinks(elements);

if (isAtBottom) {
handleBottomScroll(elements);
} else if (currentTocLinks.length > 0) {
const tocRect = elements.tocContainer.getBoundingClientRect();
const linkElements = currentTocLinks
.map(link => link.closest('li'))
.filter((li): li is HTMLLIElement => li !== null);
if (linkElements.length === 0) return;
const firstLinkRect = linkElements[0].getBoundingClientRect();
const lastLinkRect = linkElements[linkElements.length - 1].getBoundingClientRect();
updateProgressIndicatorPosition(
elements.progressIndicator,
firstLinkRect.top - tocRect.top,
(lastLinkRect.top + lastLinkRect.height) - firstLinkRect.top
);
}
}

function setupSmoothScrolling(elements: TocElements) {
elements.tocLinks.forEach(link => {
link.addEventListener('click', (e) => {
const href = link.getAttribute('href');
if (href?.charAt(0) === '#') {
e.preventDefault();
const target = $(href.replace('.', '\\.'));
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
history.pushState(null, '', href);
}
}
});
});
}

export function initTocNav() {
const elements = initializeTocElements();
elements.progressIndicator.style.height = '0';
elements.progressIndicator.style.top = '0';
const update = () => updateIndicator(elements)
update();
window.addEventListener('scroll', update);
window.addEventListener('resize', update);
setupSmoothScrolling(elements);
}
8 changes: 4 additions & 4 deletions src/Elastic.Markdown/Elastic.Markdown.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@

<Target Name="EmbedGeneratedAssets" AfterTargets="NpmRunBuild">
<ItemGroup>
<EmbeddedResource Include="_static/*.js" Watch="false" />
<EmbeddedResource Include="_static/*.js.map" Watch="false" />
<EmbeddedResource Include="_static/*.css" Watch="false" />
<EmbeddedResource Include="_static/*.css.map" Watch="false" />
<EmbeddedResource Include="_static/*.js" />
<EmbeddedResource Include="_static/*.js.map" />
<EmbeddedResource Include="_static/*.css" />
<EmbeddedResource Include="_static/*.css.map" />
<EmbeddedResource Include="_static/*.svg" Watch="false" />
<EmbeddedResource Include="_static/*.woff2" Watch="false" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,13 @@ private void ReadDocumentInstructions(MarkdownDocument document)
var contents = document
.Descendants<HeadingBlock>()
.Where(block => block is { Level: >= 2 })
.Select(h => (h.GetData("header") as string, h.GetData("anchor") as string))
.Select(h => (h.GetData("header") as string, h.GetData("anchor") as string, h.Level))
.Select(h =>
{
var header = h.Item1!.StripMarkdown();
if (header.AsSpan().ReplaceSubstitutions(subs, out var replacement))
header = replacement;
return new PageTocItem { Heading = header!, Slug = (h.Item2 ?? header).Slugify() };
return new PageTocItem { Heading = header!, Slug = (h.Item2 ?? header).Slugify(), Level = h.Level };
})
.ToList();

Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Slices/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public LayoutViewModel LayoutModel => new()
{
Title = $"Elastic Documentation: {Model.Title}",
PageTocItems = Model.PageTocItems,
PageTocItems = Model.PageTocItems.Where(i => i is { Level: 2 or 3 }).ToList(),
Tree = Model.Tree,
CurrentDocument = Model.CurrentDocument,
Previous = Model.PreviousDocument,
Expand Down
9 changes: 8 additions & 1 deletion src/Elastic.Markdown/Slices/Layout/_Breadcrumbs.cshtml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@inherits RazorSlice<LayoutViewModel>
<ol class="flex-1 mb-6" itemscope="" itemtype="https://schema.org/BreadcrumbList">
<ol class="block w-full" itemscope="" itemtype="https://schema.org/BreadcrumbList">
<li class="inline text-ink text-sm hover:text-ink leading-[1.2em] tracking-[-0.02em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemprop="item" href="@Model.UrlPathPrefix/">
<span itemprop="name" class="hover:text-ink">Elastic</span>
Expand All @@ -16,4 +16,11 @@
<meta itemprop="position" content="2">
</li>
}
<li class="inline text-gray-500 text-sm leading-[1.2em] tracking-[-0.02em]" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<span class="px-1">/</span>
<a itemprop="item" href="@Model.CurrentDocument.Url" class="pointer-events-none">
<span itemprop="name" class="hover:text-ink">@Model.CurrentDocument.NavigationTitle</span>
</a>
<meta itemprop="position" content="1">
</li>
</ol>
Loading