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 %}