diff --git a/zola/sass/components/_preview.css b/zola/sass/components/_preview.css new file mode 100644 index 00000000..f1c49e11 --- /dev/null +++ b/zola/sass/components/_preview.css @@ -0,0 +1,25 @@ +.preview { + position: fixed; + max-height: 300px; + min-height: 100px; + width: 400px; + padding: 0; + overflow-y: auto; + border-bottom: none; + background-color: #ffffff; + border: 1px solid #f5f6f8; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 6px; + color: black; + padding: 15px 30px; + font-size: 15px; + overscroll-behavior: contain; + z-index: 9999999; +} + +body.dark .preview { + background-color: #212529; + border: 1px solid #39383b; + box-shadow: 0 2px 8px gba(0, 0, 0, 0.3);; + color: white; +} \ No newline at end of file diff --git a/zola/sass/main.scss b/zola/sass/main.scss index ca3e4f0f..b48440b8 100644 --- a/zola/sass/main.scss +++ b/zola/sass/main.scss @@ -13,6 +13,7 @@ @import "components/doks"; // @import "components/syntax"; +@import "components/preview"; @import "components/code"; @import "components/alerts"; @import "components/buttons"; diff --git a/zola/static/js/preview.js b/zola/static/js/preview.js new file mode 100644 index 00000000..568a6737 --- /dev/null +++ b/zola/static/js/preview.js @@ -0,0 +1,132 @@ +const cache = new Map(); + +function showPreview(mouseEvent, link) { + const { clientX, clientY } = mouseEvent; + let previewDiv = createPreview(); + + previewDiv.innerHTML = "Loading..."; + + const html = cache.get(link.href); + if (!html) { + fetch(`${link.href}`) + .then((res) => res.text()) + .then((html) => { + let doc = new DOMParser().parseFromString(html, "text/html"); + let docContent = doc.querySelector(".docs-content"); + previewDiv.innerHTML = docContent.innerHTML; + + let blockId = link.href.match(/(?<=#).{6}/); + + if (blockId != null) { + blockId = [blockId]; + const blockContent = [ + ...docContent.querySelectorAll( + "p, li, h1, h2, h3, h4, h5, h6" + ), + ].findLast((e) => { + return e.textContent.includes(`^${blockId}`); + }); + + previewDiv.innerHTML = blockContent.outerHTML; + } + cache.set(link.href, previewDiv.innerHTML); + initPreview(`.${getPreviewUniqueClass(previewDiv)} a`); + }); + } else { + previewDiv.innerHTML = html; + initPreview(`.${getPreviewUniqueClass(previewDiv)} a`); + } + + const { top, right } = getPreviewPosition(clientX, clientY); + previewDiv.style.top = `${top}px`; + previewDiv.style.right = `${right}px`; + + previewDiv.addEventListener("mouseleave", () => { + handleMouseLeave(); + }); + + link.addEventListener( + "mouseleave", + () => { + setTimeout(() => { + if (!previewDiv.matches(":hover")) { + hidePreview(previewDiv); + } + }, 200); + }, + false + ); +} + +function getPreviewPosition(clientX, clientY) { + const offset = 10, + previewDivWidth = 400, + previewDivHeight = 100; + const boundaryX = window.innerWidth, + boundaryY = window.innerHeight; + const overflowRight = clientX + offset + previewDivWidth > boundaryX; + const overflowLeft = clientX - offset - previewDivWidth < 0; + const overflowBottom = clientY + offset + previewDivHeight > boundaryY; + const position = { top: offset, right: offset }; + + if (!overflowRight) { + position.right = boundaryX - clientX - offset - previewDivWidth; + } else if (!overflowLeft) { + position.right = boundaryX - clientX - offset; + } + + if (!overflowBottom) { + position.top = clientY + offset; + } else { + position.top = clientY - offset - previewDivHeight; + } + + return position; +} + +function handleMouseLeave() { + setTimeout(() => { + const allPreviews = document.querySelectorAll(".preview"); + for (let i = allPreviews.length - 1; i >= 0; i--) { + const curr = allPreviews[i]; + if (curr.matches(":hover")) { + break; + } + hidePreview(curr); + } + }, 300); +} + +function getPreviewUniqueClass(previewDiv) { + return previewDiv.classList.item(previewDiv.classList.length - 1); +} + +function isDocLink(href) { + const test = new URL(href); + return test.pathname.startsWith("/docs/"); +} + +function hidePreview(previewDiv) { + try { + document.body.removeChild(previewDiv); + } catch (e) {} +} + +function createPreview() { + const previewDiv = document.createElement("div"); + const uniqueClassName = (Math.random() + 1).toString(36).substring(7); + previewDiv.classList.add("preview"); + previewDiv.classList.add(`preview_${uniqueClassName}`); + document.querySelector("body").appendChild(previewDiv); + return previewDiv; +} + +function initPreview(query = ".docs-content a") { + document.querySelectorAll(query).forEach((a) => { + if (isDocLink(a.href)) { + a.addEventListener("mouseover", (e) => showPreview(e, a), false); + } + }); +} + +initPreview(); diff --git a/zola/templates/macros/javascript.html b/zola/templates/macros/javascript.html index 58bb6e42..2df23e7a 100644 --- a/zola/templates/macros/javascript.html +++ b/zola/templates/macros/javascript.html @@ -1,6 +1,7 @@ {% macro javascript() %} + {% if config.build_search_index %}