From 9d87c92c68bad5b35407bad4601434f41530fd5d Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 20 Feb 2025 11:05:27 +0100 Subject: [PATCH 1/3] Add unpoly --- src/Elastic.Markdown/Assets/copybutton.ts | 23 ++--- src/Elastic.Markdown/Assets/hljs.ts | 4 +- src/Elastic.Markdown/Assets/main.ts | 24 ++++- src/Elastic.Markdown/Assets/pages-nav.ts | 98 +++---------------- src/Elastic.Markdown/Assets/styles.css | 32 +++++- src/Elastic.Markdown/Assets/tabs.ts | 15 +-- src/Elastic.Markdown/Assets/toc-nav.ts | 6 +- .../Slices/Layout/_PagesNav.cshtml | 4 +- .../Slices/Layout/_TableOfContents.cshtml | 2 +- .../Slices/Layout/_TocTree.cshtml | 4 +- .../Slices/Layout/_TocTreeNav.cshtml | 11 ++- src/Elastic.Markdown/Slices/_Layout.cshtml | 20 ++-- 12 files changed, 103 insertions(+), 140 deletions(-) diff --git a/src/Elastic.Markdown/Assets/copybutton.ts b/src/Elastic.Markdown/Assets/copybutton.ts index 35cb8da22..31bbc5f2d 100644 --- a/src/Elastic.Markdown/Assets/copybutton.ts +++ b/src/Elastic.Markdown/Assets/copybutton.ts @@ -95,19 +95,6 @@ if (!iconCopy) { /** * Set up copy/paste for code blocks */ - -const runWhenDOMLoaded = cb => { - if (document.readyState != 'loading') { - cb() - } else if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', cb) - } else { - document.attachEvent('onreadystatechange', function() { - if (document.readyState == 'complete') cb() - }) - } -} - const codeCellId = index => `codecell${index}` // Clears selected text since ClipboardJS will select the text when copying @@ -145,9 +132,14 @@ 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) @@ -256,6 +248,5 @@ var copyTargetText = (trigger) => { } export function initCopyButton() { - console.log("initCopyButton"); - runWhenDOMLoaded(addCopyButtonToCodeCells) + addCopyButtonToCodeCells() } diff --git a/src/Elastic.Markdown/Assets/hljs.ts b/src/Elastic.Markdown/Assets/hljs.ts index 675963dc4..c1e80e0d2 100644 --- a/src/Elastic.Markdown/Assets/hljs.ts +++ b/src/Elastic.Markdown/Assets/hljs.ts @@ -164,6 +164,6 @@ 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 as HTMLElement); }); } diff --git a/src/Elastic.Markdown/Assets/main.ts b/src/Elastic.Markdown/Assets/main.ts index e3b7a4e78..ff1534cf3 100644 --- a/src/Elastic.Markdown/Assets/main.ts +++ b/src/Elastic.Markdown/Assets/main.ts @@ -1,9 +1,23 @@ import {initTocNav} from "./toc-nav"; -import {initHighlight} from "./hljs"; import {initTabs} from "./tabs"; import {initCopyButton} from "./copybutton"; +import {initNav} from "./pages-nav"; +import {initHighlight} from "./hljs"; + + + +up.link.config.followSelectors.push('a[href]') +up.link.config.preloadSelectors.push('a[href]') + -initTocNav(); -initHighlight(); -initCopyButton(); -initTabs(); +up.compiler('.markdown-content, #toc-nav', () => { + window.scrollTo(0, 0); + const destroyTocNav = initTocNav(); + initNav(); + initHighlight(); + initCopyButton(); + initTabs(); + return () => { + destroyTocNav(); + } +}) diff --git a/src/Elastic.Markdown/Assets/pages-nav.ts b/src/Elastic.Markdown/Assets/pages-nav.ts index ff9df8073..6660cdb94 100644 --- a/src/Elastic.Markdown/Assets/pages-nav.ts +++ b/src/Elastic.Markdown/Assets/pages-nav.ts @@ -1,87 +1,21 @@ -import {$, $$} from "select-dom"; - -type NavExpandState = { - current:string, - selected: Record -}; -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'); - } +import {$} from "select-dom"; + +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 = inputs - .filter(input => input.checked) - .reduce((state: Record, 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 = $('.up-current', nav); + expandAllParents(currentNavItem); setTimeout(() => { - const currentNavItem = $('.current', nav); + if (currentNavItem && !isElementInViewport(currentNavItem)) { currentNavItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); } @@ -102,13 +36,5 @@ export function initNav() { if (!pagesNav) { return; } - const keepNavStateCallback = keepNavState(pagesNav); - const keepNavPositionCallback = keepNavPosition(pagesNav); scrollCurrentNaviItemIntoView(pagesNav, 100); - window.addEventListener('beforeunload', () => { - keepNavStateCallback(); - keepNavPositionCallback(); - }, true); } - -initNav(); diff --git a/src/Elastic.Markdown/Assets/styles.css b/src/Elastic.Markdown/Assets/styles.css index e16fa0016..bc8b72fe5 100644 --- a/src/Elastic.Markdown/Assets/styles.css +++ b/src/Elastic.Markdown/Assets/styles.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +/*@import "unpoly/unpoly.css";*/ @import "./fonts.css"; @import "./theme.css"; @import "./markdown/typography.css"; @@ -85,7 +86,7 @@ } .content-container { - @apply w-full max-w-[80ch] lg:w-[80ch]; + @apply w-full max-w-[80ch]; } .applies { @@ -119,9 +120,9 @@ :root { --outline-size: max(2px, 0.08em); - --outline-style: auto; + --outline-style: solid; --outline-color: var(--color-blue-elastic); - --outline-offset: 5; + --outline-offset: 10; } :is(a, button, input, textarea, summary):focus { @@ -137,3 +138,28 @@ :is(a, button, input, textarea, summary):focus:not(:focus-visible) { outline: none; } + +up-progress-bar { + @apply bg-pink-90!; +} + +.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 { + .up-current { + @apply text-blue-elastic!; + } +} diff --git a/src/Elastic.Markdown/Assets/tabs.ts b/src/Elastic.Markdown/Assets/tabs.ts index cfc692dd0..dbf961e04 100644 --- a/src/Elastic.Markdown/Assets/tabs.ts +++ b/src/Elastic.Markdown/Assets/tabs.ts @@ -34,7 +34,7 @@ function ready() { /** @type {string[]} */ let groups = []; - document.querySelectorAll(".tabs-label").forEach((label) => { + document.querySelectorAll(".markdown-content .tabs-label").forEach((label) => { if (label instanceof HTMLElement) { let data = create_key(label); if (data) { @@ -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); } } @@ -72,10 +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; } } @@ -101,6 +91,5 @@ function onSDLabelClick() { } export function initTabs() { - console.log("inittabs"); - document.addEventListener("DOMContentLoaded", ready, false); + ready(); } diff --git a/src/Elastic.Markdown/Assets/toc-nav.ts b/src/Elastic.Markdown/Assets/toc-nav.ts index 61bbb950b..9638ea4d8 100644 --- a/src/Elastic.Markdown/Assets/toc-nav.ts +++ b/src/Elastic.Markdown/Assets/toc-nav.ts @@ -126,7 +126,7 @@ function setupSmoothScrolling(elements: TocElements) { }); } -export function initTocNav() { +export function initTocNav(): () => void { const elements = initializeTocElements(); if (elements.progressIndicator != null) { elements.progressIndicator.style.height = '0'; @@ -137,4 +137,8 @@ export function initTocNav() { window.addEventListener('scroll', update); window.addEventListener('resize', update); setupSmoothScrolling(elements); + return () => { + window.removeEventListener('scroll', update); + window.removeEventListener('resize', update); + } } diff --git a/src/Elastic.Markdown/Slices/Layout/_PagesNav.cshtml b/src/Elastic.Markdown/Slices/Layout/_PagesNav.cshtml index 52d8ed4db..26ba5bbec 100644 --- a/src/Elastic.Markdown/Slices/Layout/_PagesNav.cshtml +++ b/src/Elastic.Markdown/Slices/Layout/_PagesNav.cshtml @@ -1,8 +1,8 @@ @inherits RazorSlice -