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
9 changes: 6 additions & 3 deletions src/Elastic.Markdown/Assets/copybutton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,13 @@ const addCopyButtonToCodeCells = () => {
// happens because we load ClipboardJS asynchronously.

// Add copybuttons to all of our code cells
const COPYBUTTON_SELECTOR = 'div.highlight pre';
const COPYBUTTON_SELECTOR = '.markdown-content div.highlight pre';
const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
codeCells.forEach((codeCell, index) => {
if (codeCell.id) {
return
}

const id = codeCellId(index)
codeCell.setAttribute('id', id)

Expand Down Expand Up @@ -256,6 +260,5 @@ var copyTargetText = (trigger) => {
}

export function initCopyButton() {
console.log("initCopyButton");
runWhenDOMLoaded(addCopyButtonToCodeCells)
addCopyButtonToCodeCells();
}
5 changes: 3 additions & 2 deletions src/Elastic.Markdown/Assets/hljs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ hljs.registerLanguage('esql', function() {

hljs.addPlugin(mergeHTMLPlugin);
export function initHighlight() {

hljs.highlightAll();
document.querySelectorAll('#markdown-content pre code:not(.hljs)').forEach((block) => {
hljs.highlightElement(block);
});
}
33 changes: 29 additions & 4 deletions src/Elastic.Markdown/Assets/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import "htmx.org"
import "htmx-ext-preload"
import {initTocNav} from "./toc-nav";
import {initHighlight} from "./hljs";
import {initTabs} from "./tabs";
import {initCopyButton} from "./copybutton";
import {initNav} from "./pages-nav";
import {$$} from "select-dom"

initTocNav();
initHighlight();
initCopyButton();
initTabs();
document.addEventListener('htmx:load', function() {
initTocNav();
initHighlight();
initCopyButton();
initTabs();
initNav();
});

document.body.addEventListener('htmx:oobAfterSwap', function(event) {
if (event.target.id === 'markdown-content') {
window.scrollTo(0, 0);
}
});

document.body.addEventListener('htmx:pushedIntoHistory', function(event) {
const currentNavItem = $$('.current');
currentNavItem.forEach(el => {
el.classList.remove('current');
})
// @ts-ignore
const navItems = $$('a[href="' + event.detail.path + '"]');
navItems.forEach(navItem => {
navItem.classList.add('current');
});
});
99 changes: 16 additions & 83 deletions src/Elastic.Markdown/Assets/pages-nav.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,21 @@
import {$, $$} from "select-dom";

type NavExpandState = {
current:string,
selected: Record<string, boolean>
};
const PAGE_NAV_EXPAND_STATE_KEY = 'pagesNavState';

// 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 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;
const link = input.nextElementSibling as HTMLAnchorElement;
link.classList.add('font-semibold');
}
function expandAllParents(navItem: HTMLElement) {
let parent = navItem?.closest('li');
while (parent) {
const input = parent.querySelector('input');
if (input) {
(input as HTMLInputElement).checked = true;
}
}

// 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 () => {
// 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));
}
}

type NavScrollPosition = number;
const PAGE_NAV_SCROLL_POSITION_KEY = 'pagesNavScrollPosition';
const pagesNavScrollPosition: NavScrollPosition = parseInt(
sessionStorage.getItem(PAGE_NAV_SCROLL_POSITION_KEY) ?? '0'
);


// Initialize the nav scroll position from the session storage
// Return a function to keep the nav scroll position in the session storage that should be called before the page is unloaded
function keepNavPosition(nav: HTMLElement): () => void {
if (pagesNavScrollPosition) {
nav.scrollTop = pagesNavScrollPosition;
}
return () => {
sessionStorage.setItem(PAGE_NAV_SCROLL_POSITION_KEY, nav.scrollTop.toString());
parent = parent.parentElement?.closest('li');
}
}

function scrollCurrentNaviItemIntoView(nav: HTMLElement, delay: number) {
const currentNavItem = $('.current', nav);
expandAllParents(currentNavItem);
setTimeout(() => {
const currentNavItem = $('.current', nav);

if (currentNavItem && !isElementInViewport(currentNavItem)) {
currentNavItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
Expand All @@ -102,13 +36,12 @@ export function initNav() {
if (!pagesNav) {
return;
}
const keepNavStateCallback = keepNavState(pagesNav);
const keepNavPositionCallback = keepNavPosition(pagesNav);
const navItems = $$('a[href="' + window.location.pathname + '"]', pagesNav);
navItems.forEach(el => {
el.classList.add('current');
});
scrollCurrentNaviItemIntoView(pagesNav, 100);
window.addEventListener('beforeunload', () => {
keepNavStateCallback();
keepNavPositionCallback();
}, true);
}

initNav();

// initNav();
34 changes: 33 additions & 1 deletion src/Elastic.Markdown/Assets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
}

.content-container {
@apply w-full max-w-[80ch] lg:w-[80ch];
@apply w-full max-w-[80ch];
}

.applies {
Expand Down Expand Up @@ -139,6 +139,38 @@
outline: none;
}

.htmx-indicator {
display:none;
}
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator{
display:block;
z-index: 9999;
}

.progress {
animation: progress 1s infinite linear;
}

.left-right {
transform-origin: 0% 50%;
}
@keyframes progress {
0% {
transform: translateX(0) scaleX(0);
}
40% {
transform: translateX(0) scaleX(0.4);
}
100% {
transform: translateX(100%) scaleX(0.5);
}
}

#pages-nav .current {
@apply text-blue-elastic!;
}

.markdown-content {
@apply font-body;
}
12 changes: 1 addition & 11 deletions src/Elastic.Markdown/Assets/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ function ready() {
group
);
if (tabParam) {
console.log(
"sphinx-design: Selecting tab id for group '" +
group +
"' from URL parameter: " +
tabParam
);
window.sessionStorage.setItem(storageKeyPrefix + group, tabParam);
}
}
Expand All @@ -72,9 +66,6 @@ function ready() {
storageKeyPrefix + group
);
if (previousId === id) {
// console.log(
// "sphinx-design: Selecting tab from session storage: " + id
// );
// @ts-ignore
label.previousElementSibling.checked = true;
}
Expand All @@ -101,6 +92,5 @@ function onSDLabelClick() {
}

export function initTabs() {
console.log("inittabs");
document.addEventListener("DOMContentLoaded", ready, false);
ready();
}
2 changes: 2 additions & 0 deletions src/Elastic.Markdown/Myst/MarkdownParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Elastic.Markdown.Myst.Directives;
using Elastic.Markdown.Myst.FrontMatter;
using Elastic.Markdown.Myst.InlineParsers;
using Elastic.Markdown.Myst.Renderers;
using Elastic.Markdown.Myst.Substitution;
using Markdig;
using Markdig.Extensions.EmphasisExtras;
Expand Down Expand Up @@ -80,6 +81,7 @@ public static MarkdownPipeline Pipeline
.UseDirectives()
.UseDefinitionLists()
.UseEnhancedCodeBlocks()
.UseHtmxLinkInlineRenderer()
.DisableHtml()
.UseHardBreaks();
_ = builder.BlockParsers.TryRemove<IndentedCodeBlockParser>();
Expand Down
77 changes: 77 additions & 0 deletions src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using Markdig;
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;

namespace Elastic.Markdown.Myst.Renderers;

public class HtmxLinkInlineRenderer : LinkInlineRenderer
{
protected override void Write(HtmlRenderer renderer, LinkInline link)
{
if (renderer.EnableHtmlForInline && !link.IsImage && link.Url?.StartsWith('/') == true)
{
_ = renderer.Write("<a href=\"");
_ = renderer.WriteEscapeUrl(link.GetDynamicUrl != null ? link.GetDynamicUrl() ?? link.Url : link.Url);
_ = renderer.Write('"');
_ = renderer.WriteAttributes(link);
_ = renderer.Write(" hx-select-oob=\"#markdown-content,#toc-nav,#prev-next-nav\"");
_ = renderer.Write(" hx-swap=\"none\"");
_ = renderer.Write(" hx-push-url=\"true\"");
_ = renderer.Write(" hx-indicator=\"#htmx-indicator\"");
_ = renderer.Write(" preload=\"mouseover\"");

if (!string.IsNullOrEmpty(link.Title))
{
_ = renderer.Write(" title=\"");
_ = renderer.WriteEscape(link.Title);
_ = renderer.Write('"');
}
if (!string.IsNullOrWhiteSpace(Rel))
{
_ = renderer.Write(" rel=\"");
_ = renderer.Write(Rel);
_ = renderer.Write('"');
}

_ = renderer.Write('>');
renderer.WriteChildren(link);

_ = renderer.Write("</a>");
}
else
{
base.Write(renderer, link);
}
}
}

public static class CustomLinkInlineRendererExtensions
{
public static MarkdownPipelineBuilder UseHtmxLinkInlineRenderer(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<HtmxLinkInlineRendererExtension>();
return pipeline;
}
}

public class HtmxLinkInlineRendererExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
// No setup required for the pipeline
}

public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer)
{
_ = htmlRenderer.ObjectRenderers.RemoveAll(x => x is LinkInlineRenderer);
htmlRenderer.ObjectRenderers.Add(new HtmxLinkInlineRenderer());
}
}
}
9 changes: 5 additions & 4 deletions src/Elastic.Markdown/Slices/Layout/_PagesNav.cshtml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
@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"
<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"
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>
</aside>
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Slices/Layout/_TocTree.cshtml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@inherits RazorSlice<NavigationViewModel>
<div class="pt-6 pb-20">
<div class="font-bold">Elastic Docs</div>
<ul class="block w-full">
<ul class="block">
@await RenderPartialAsync(_TocTreeNav.Create(new NavigationTreeItem
{
Level = Model.Tree.Depth,
Expand Down
Loading