Merge pull request #20743 from dvdksn/unify-search-ai

site: improve search ux, add /search page
This commit is contained in:
David Karlsson 2024-09-12 17:22:16 +02:00 committed by GitHub
commit dc7819909e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 446 additions and 183 deletions

View File

@ -16,7 +16,24 @@
}
mark {
@apply bg-amber-light-200 dark:bg-amber-dark-600/25 dark:text-white;
@apply bg-transparent font-bold text-blue-light dark:text-blue-dark;
}
/* Hide the clear (X) button for search inputs */
/* Chrome, Safari, Edge, and Opera */
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
}
/* Firefox */
input[type="search"]::-moz-search-cancel-button {
display: none;
}
/* Internet Explorer and Edge (legacy) */
input[type="search"]::-ms-clear {
display: none;
}
}

View File

@ -0,0 +1,14 @@
<svg width="16" height="16" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_225_286" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="1" width="22" height="23">
<path d="M18.5 8.646V3.646M5.5 21.646V16.646M16 6.146H21M3 19.146H8M6.5 2.646L5.71554 4.21491C5.45005 4.74589 5.31731 5.01138 5.13997 5.24144C4.98261 5.44559 4.79959 5.6286 4.59545 5.78597C4.36538 5.9633 4.0999 6.09605 3.56892 6.36154L2 7.146L3.56892 7.93045C4.0999 8.19594 4.36538 8.32869 4.59545 8.50603C4.79959 8.66339 4.98261 8.8464 5.13997 9.05055C5.31731 9.28061 5.45005 9.5461 5.71554 10.0771L6.5 11.646L7.28446 10.0771C7.54995 9.5461 7.68269 9.28061 7.86003 9.05055C8.01739 8.8464 8.20041 8.66339 8.40455 8.50603C8.63462 8.32869 8.9001 8.19594 9.43108 7.93045L11 7.146L9.43108 6.36154C8.9001 6.09605 8.63462 5.9633 8.40455 5.78597C8.20041 5.6286 8.01739 5.44559 7.86003 5.24144C7.68269 5.01138 7.54995 4.74589 7.28446 4.21491L6.5 2.646ZM17 12.646L16.0489 14.5482C15.7834 15.0792 15.6506 15.3447 15.4733 15.5748C15.3159 15.7789 15.1329 15.9619 14.9288 16.1193C14.6987 16.2966 14.4332 16.4294 13.9023 16.6949L12 17.646L13.9023 18.5971C14.4332 18.8626 14.6987 18.9954 14.9288 19.1727C15.1329 19.3301 15.3159 19.5131 15.4733 19.7172C15.6506 19.9473 15.7834 20.2128 16.0489 20.7437L17 22.646L17.9511 20.7437C18.2166 20.2128 18.3494 19.9473 18.5267 19.7172C18.6841 19.5131 18.8671 19.3301 19.0712 19.1727C19.3013 18.9954 19.5668 18.8626 20.0977 18.5971L22 17.646L20.0977 16.6949C19.5668 16.4294 19.3013 16.2966 19.0712 16.1193C18.8671 15.9619 18.6841 15.7789 18.5267 15.5748C18.3494 15.3447 18.2166 15.0792 17.9511 14.5482L17 12.646Z" stroke="#677285" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
<g mask="url(#mask0_225_286)">
<rect y="0.799316" width="24" height="24" fill="url(#paint0_linear_225_286)"/>
</g>
<defs>
<linearGradient id="paint0_linear_225_286" x1="0" y1="24.7993" x2="24" y2="0.799316" gradientUnits="userSpaceOnUse">
<stop stop-color="#FBB552"/>
<stop offset="1" stop-color="#E950E2"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,9 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_200_9677" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="2" y="0" width="21" height="23">
<path d="M21.2461 21.4956L16.8961 17.1456M19.2461 11.4956C19.2461 15.9139 15.6644 19.4956 11.2461 19.4956C6.82782 19.4956 3.24609 15.9139 3.24609 11.4956C3.24609 7.07733 6.82782 3.49561 11.2461 3.49561" stroke="#677285" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.7461 1.84082L17.5306 3.40974C17.796 3.94072 17.9288 4.2062 18.1061 4.43627C18.2635 4.64041 18.4465 4.82343 18.6506 4.98079C18.8807 5.15813 19.1462 5.29087 19.6772 5.55636L21.2461 6.34082L19.6772 7.12528C19.1462 7.39077 18.8807 7.52351 18.6506 7.70085C18.4465 7.85821 18.2635 8.04123 18.1061 8.24537C17.9288 8.47544 17.796 8.74093 17.5306 9.2719L16.7461 10.8408L15.9616 9.2719C15.6961 8.74092 15.5634 8.47544 15.3861 8.24537C15.2287 8.04123 15.0457 7.85821 14.8415 7.70085C14.6115 7.52351 14.346 7.39077 13.815 7.12528L12.2461 6.34082L13.815 5.55636C14.346 5.29087 14.6115 5.15813 14.8415 4.98079C15.0457 4.82343 15.2287 4.64041 15.3861 4.43627C15.5634 4.2062 15.6961 3.94072 15.9616 3.40974L16.7461 1.84082Z" stroke="#677285" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
<g mask="url(#mask0_200_9677)">
<rect x="0.426636" y="0.495605" width="24" height="24" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

5
content/search.md Normal file
View File

@ -0,0 +1,5 @@
---
sitemap: false
title: Search
layout: search
---

View File

@ -6,6 +6,7 @@
"--mount",
"--tmpfs",
"-mb-3",
"-mt-0.5",
"-mt-1",
"-mt-4",
"-top-16",
@ -136,13 +137,11 @@
"Without-packages",
"Without-systemd",
"absolute",
"appearance-none",
"aspect-video",
"bake-action",
"bg-accent-light",
"bg-amber-light",
"bg-amber-light-200",
"bg-background-dark/70",
"bg-background-light",
"bg-black/70",
"bg-blue-light",
@ -165,10 +164,12 @@
"bg-transparent",
"bg-violet-light",
"bg-white",
"bg-white/10",
"block",
"border",
"border-0",
"border-amber-light",
"border-b",
"border-b-2",
"border-b-4",
"border-b-gray-light-400",
@ -183,7 +184,9 @@
"border-l-4",
"border-l-magenta-light",
"border-red-light",
"border-transparent",
"border-violet-light",
"border-white",
"bottom-0",
"box-content",
"build-push-action",
@ -200,7 +203,6 @@
"dark:bg-blue-dark-400",
"dark:bg-blue-dark-500",
"dark:bg-gray-dark-100",
"dark:bg-gray-dark-100/70",
"dark:bg-gray-dark-200",
"dark:bg-gray-dark-300",
"dark:bg-gray-dark-300/50",
@ -224,17 +226,18 @@
"dark:border-violet-dark",
"dark:decoration-blue-dark",
"dark:fill-blue-dark",
"dark:focus:outline-blue-dark",
"dark:focus:ring-blue-dark",
"dark:from-accent-dark",
"dark:from-background-dark",
"dark:from-blue-dark-400",
"dark:hidden",
"dark:hover:bg-blue-dark",
"dark:hover:bg-blue-dark-500",
"dark:hover:bg-gray-dark-200",
"dark:hover:bg-gray-dark-500",
"dark:hover:text-white",
"dark:outline-gray-dark",
"dark:prose-invert",
"dark:ring-gray-dark-400",
"dark:syntax-dark",
"dark:text-amber-dark",
"dark:text-blue-dark",
@ -262,12 +265,11 @@
"fixed",
"flex",
"flex-1",
"flex-auto",
"flex-col",
"flex-col-reverse",
"flex-grow",
"flex-wrap",
"focus:outline-blue-light",
"focus:ring-blue-light",
"font-medium",
"footnote-backref",
"footnote-ref",
@ -290,7 +292,6 @@
"grid-cols-2",
"group",
"group-hover:block'",
"h-12",
"h-16",
"h-2",
"h-32",
@ -306,8 +307,10 @@
"highlight",
"hover:bg-blue-light-400",
"hover:bg-gray-light-100",
"hover:bg-gray-light-200",
"hover:bg-gray-light-300",
"hover:border-gray-light-200",
"hover:border-white/20",
"hover:dark:bg-gray-dark-300",
"hover:dark:border-gray-dark",
"hover:dark:text-blue-dark",
@ -316,6 +319,7 @@
"hover:opacity-90",
"hover:text-black",
"hover:text-blue-light",
"hover:text-white",
"hover:underline",
"icon-lg",
"icon-sm",
@ -329,6 +333,7 @@
"items-stretch",
"justify-between",
"justify-center",
"justify-end",
"justify-evenly",
"justify-self-end",
"leading-snug",
@ -344,21 +349,19 @@
"lg:scale-100",
"lg:text-base",
"lg:w-[1200px]",
"lg:w-[600px]",
"link",
"lntable",
"lntd",
"m-2",
"m-4",
"m-auto",
"macOS",
"max-h-[80vh]",
"max-h-full",
"max-w-56",
"max-w-[1400px]",
"max-w-[840px]",
"max-w-full",
"max-w-none",
"max-w-xl",
"mb-4",
"mb-8",
"md:block",
@ -374,16 +377,14 @@
"md:max-w-[66%]",
"md:px-20",
"md:scale-100",
"min-h-0",
"min-h-screen",
"min-w-0",
"ml-3",
"ml-auto",
"mt-1",
"mt-2",
"mt-20",
"mt-auto",
"mx-1",
"mx-8",
"mx-auto",
"my-0",
"my-2",
@ -394,15 +395,13 @@
"no-wrap",
"not-prose",
"object-cover",
"open-kapa-widget",
"openSUSE-and-SLES",
"origin-bottom-right",
"outline",
"outline-2",
"outline-gray-light",
"outline-none",
"overflow-clip",
"overflow-hidden",
"overflow-x-hidden",
"overflow-y-auto",
"overflow-y-scroll",
"p-1",
"p-2",
@ -422,6 +421,7 @@
"pl-4",
"pl-5",
"place-items-center",
"placeholder:text-white",
"pr-2",
"prose",
"pt-0",
@ -429,9 +429,9 @@
"px-2",
"px-3",
"px-4",
"px-6",
"px-8",
"py-1",
"py-12",
"py-2",
"py-20",
"py-4",
@ -440,6 +440,8 @@
"right-0",
"right-3",
"right-8",
"ring-[1.5px]",
"ring-gray-light-200",
"rotate-45",
"rounded",
"rounded-[6px]",
@ -456,9 +458,13 @@
"select-none",
"self-center",
"shadow",
"shadow-lg",
"sm:flex",
"sm:flex-row",
"sm:grid-cols-2",
"sm:hidden",
"sm:items-center",
"sm:w-full",
"space-x-2",
"space-y-4",
"sticky",
@ -497,6 +503,7 @@
"top-16",
"top-3",
"top-6",
"top-full",
"transition",
"truncate",
"underline",
@ -507,17 +514,20 @@
"w-8",
"w-[1200px]",
"w-[32px]",
"w-fit",
"w-full",
"w-lvw",
"w-screen",
"xl:grid-cols-3",
"xl:grid-cols-4",
"xl:grid-cols-main-xl",
"xl:w-[1200px]",
"xl:w-[800px]",
"xl:w-[400px]",
"youtube-video",
"z-10",
"z-20",
"z-30"
"z-30",
"z-50"
],
"ids": null
}

View File

@ -5,7 +5,8 @@
{{ partial "head.html" . }}
</head>
<body class="bg-background-light text-base dark:bg-background-dark dark:text-white">
<body
class="bg-background-light text-base dark:bg-background-dark dark:text-white">
{{ partial "header.html" . }}
<main class="grid grid-cols-1 xl:grid-cols-main-xl lg:grid-cols-main-lg md:grid-cols-main-md">
<!-- First column: visible on lg and xl -->

View File

@ -0,0 +1,185 @@
<!doctype html>
<html lang="en">
<head>
{{ partial "head.html" . }}
</head>
<body class="flex min-h-screen flex-col bg-background-light text-base dark:bg-background-dark dark:text-white">
{{ partial "header.html" . }}
<main class="flex justify-center">
<div class="w-lvw overflow-clip p-6 pt-0 lg:w-[1200px]">
<article class="prose max-w-none dark:prose-invert">
<h1 class="py-4">{{ .Title }}</h1>
{{ .Content }}
<div class="not-prose">
<div class="flex gap-4">
<input type="search" id="search-page-input"
class="ring-[1.5px] ring-gray-light-200 dark:ring-gray-dark-400 w-full max-w-xl rounded px-4 py-2 outline-none bg-white dark:bg-background-dark focus:ring-blue-light dark:focus:ring-blue-dark"
placeholder="Search…" tabindex="0" />
<button
class="py-1 px-4 rounded open-kapa-widget flex w-fit gap-2 items-center hover:bg-gray-light-200 dark:hover:bg-gray-dark-200">
<span>Ask&nbsp;AI</span>
<img height="24px" width="24px" src="{{ (resources.Get "images/ai-stars.svg").Permalink }}"
alt="AI sparkles!" />
</button>
</div>
<hr class="border-divider-light dark:border-divider-dark">
<div id="search-page-results">
<!-- results -->
</div>
</div>
</article>
</div>
</main>
<footer class="mt-auto">{{ partialCached "footer.html" . }}</footer>
<script type="module">
// Global variable to hold the pagefind module
let pagefind;
// Initialize the pagefind module and fire a search if the query parameter exists
window.addEventListener("load", async function () {
// Hydrate pagefind
pagefind = await import("/pagefind/pagefind.js");
// Get the query parameter from the URL
const urlParams = new URLSearchParams(window.location.search);
const query = urlParams.get("q");
// If no query parameter is set, return
if (!query) {
return;
}
const searchInput = document.getElementById("search-page-input");
// Set the value of the input field to the query parameter
searchInput.value = query;
// Trigger the input event to simulate user typing
const event = new Event("input", {
bubbles: true,
cancelable: true,
});
// Trigger the input event for the search input
searchInput.dispatchEvent(event);
searchInput.focus();
});
const searchPageInput = document.querySelector("#search-page-input");
const searchPageResults = document.querySelector("#search-page-results");
// onPageSearch returns 10 results per query
async function onPageSearch(e) {
pagefind.init();
const query = e.target.value;
// Set the query parameter in the URL
const params = new URLSearchParams(document.location.search);
params.set("q", query);
// Default the current page to 1
let currentPage = 1;
// Check if the page parameter exists
const page = params.get("page");
// Calculate the range start based on the page parameter
if (page) {
currentPage = parseInt(page);
}
const rangeStart = (currentPage - 1) * 10;
const rangeEnd = rangeStart + 10;
// Execute the search
const search = await pagefind.debouncedSearch(query);
// If no search results are found, exit
if (search === null) {
return;
} else {
// total number of results
const resultsLength = search.results.length;
// Get the data for the search results
// Slice the results based on the range start + 10
const resultsData = await Promise.all(
search.results.slice(rangeStart, rangeEnd).map((r) => r.data()),
);
// If the range does not have any results, display a message
if (resultsData.length === 0) {
searchPageResults.innerHTML = `<div class="p-4">No results found</div>`;
return;
}
// Add an index to the results, for heap tracking
const results = resultsData.map((item, index) => ({
...item,
index: index + 1,
}));
// If the query is not empty, display the search results container
if (query) {
searchPageResults.classList.remove("hidden");
} else {
searchPageResults.classList.add("hidden");
}
// Generate the search results HTML
let resultsHTML = `<div class="text-gray-light dark:text-gray-dark p-2">${resultsLength} results</div>`;
// Map results to HTML
resultsHTML += results
.map((item) => {
return `<div class="p-4">
<div class="flex flex-col">
<span class="text-gray-light dark:texty-gray-dark text-sm">${item.meta.breadcrumbs}</span>
<a class="link" href="${item.url}" data-query="${query}" data-index="${item.index}">${item.meta.title}</a>
<p class="text-black dark:text-white overflow-hidden">…${item.excerpt}…</p>
</div>
</div>`;
})
.join("");
// If the results length is greater than 10, display links to show more results
if (resultsLength > 10) {
resultsHTML += `<hr class="border-divider-light dark:border-divider-dark">`
resultsHTML += `<ul class="flex flex-wrap gap-1 pt-4 pb-8 justify-center text-sm">`;
for (let i = 1; i <= resultsLength / 10; i++) {
if (i == currentPage) {
resultsHTML += `<li class="text-center text-white">
<a href="/search?q=${query}&page=${i}" class="block h-6 w-6 rounded-sm bg-blue-light dark:bg-blue-dark">${i}</a>
</li>`;
} else {
resultsHTML += `<li class="text-center text-gray-light dark:text-gray-dark">
<a href="/search?q=${query}&page=${i}" class="block h-6 w-6 rounded-sm bg-gray-light-200 dark:bg-gray-dark-200">${i}</a>
</li>`;
}
}
resultsHTML += `</ul>`;
}
searchPageResults.innerHTML = resultsHTML;
}
}
searchPageInput.addEventListener("input", (e) => onPageSearch(e));
// Event delegation for tracking link clicks
if (window.heap !== undefined) {
searchPageResults.addEventListener("click", function (event) {
if (event.target.tagName === "A" && event.target.closest(".link")) {
const searchQuery = event.target.getAttribute("data-query");
const resultIndex = event.target.getAttribute("data-index");
const url = new URL(event.target.href);
const properties = {
docs_search_target_path: url.pathname,
docs_search_target_title: event.target.textContent,
docs_search_query_text: searchQuery,
docs_search_target_index: resultIndex,
docs_search_source_path: window.location.pathname,
docs_search_source_title: document.title,
};
heap.track("Docs - Search - Click - Result Link", properties);
}
});
}
</script>
</body>
</html>

View File

@ -1,4 +1,10 @@
<nav id="breadcrumbs" data-pagefind-ignore class="py-4 gap-4 flex items-center text-gray-light dark:text-gray-dark max-w-full min-w-0">
<nav id="breadcrumbs"
{{- $breadcrumbTitles := slice }}
{{ range .Ancestors.Reverse }}
{{ $breadcrumbTitles = $breadcrumbTitles | append .LinkTitle }}
{{ end }}
data-pagefind-meta="breadcrumbs:{{ collections.Delimit $breadcrumbTitles " / " }}"
data-pagefind-ignore class="py-4 gap-4 flex items-center text-gray-light dark:text-gray-dark max-w-full min-w-0">
{{ range .Ancestors.Reverse }}
<a href="{{ .Permalink }}" class="link truncate">{{ markdownify .LinkTitle }}</a>
<span>/</span>

View File

@ -1,5 +1,16 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="module">
const pagefind = await import("/pagefind/pagefind.js");
await pagefind.options({
ranking: {
termFrequency: 0.2,
pageLength: 0.75,
termSaturation: 1.4,
termSimilarity: 6.0,
},
});
</script>
{{ partial "meta.html" . }}
{{- if hugo.IsProduction -}}
<script
@ -49,33 +60,28 @@
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
</script>
{{ end }}
{{ if hugo.IsProduction }}
{{/* kapa.ai widget */}}
<script>
document.addEventListener("DOMContentLoaded", function () {
var script = document.createElement('script');
script.src = 'https://widget.kapa.ai/kapa-widget.bundle.js';
script.setAttribute('data-website-id', '{{ site.Params.kapa.id }}');
script.setAttribute('data-project-name', 'Docker');
script.setAttribute('data-user-analytics-fingerprint-enabled', 'true');
script.setAttribute('data-button-image-height', '29px');
script.setAttribute('data-button-image-width', '40px');
script.setAttribute('data-modal-image-height', '29px');
script.setAttribute('data-modal-image-width', '40px');
script.setAttribute('data-modal-title-color', '#fff');
script.setAttribute('data-project-color', '#086dd7');
script.setAttribute('data-modal-header-bg-color', '#086dd7');
script.setAttribute('data-project-logo', '/assets/images/logo-icon-white.svg');
script.setAttribute('data-font-family', 'Roboto Flex,sans-serif');
script.setAttribute('data-modal-disclaimer', 'This is a custom LLM for answering questions about Docker. Answers are based on the contents of the documentation. This feature is experimental - rate the answers to let us know what you think!');
script.setAttribute('data-modal-disclaimer-bg-color', '#e5f2fc');
script.setAttribute('data-modal-disclaimer-text-color', '#086dd7');
script.setAttribute('data-kapa-branding-text', 'powered by [kapa.ai](https://www.kapa.ai) and Docker');
script.async = true;
document.body.appendChild(script);
});
</script>
{{ end }}
{{/* kapa.ai widget */}}
<script async src="https://widget.kapa.ai/kapa-widget.bundle.js"
data-button-hide="true"
data-font-family="Roboto Flex,sans-serif"
data-modal-close-button-hide="true"
data-modal-disclaimer-bg-color="#e5f2fc"
data-modal-disclaimer-text-color="#086dd7"
data-modal-disclaimer="This is a custom LLM for answering questions about Docker. Answers are based on the contents of the documentation. Rate the answers to let us know what you think!"
data-kapa-branding-text="powered by [kapa.ai](https://www.kapa.ai) and Docker"
data-modal-header-bg-color="#1d63ed"
data-modal-image-height="25px"
data-modal-image-width="181px"
data-modal-title=""
data-modal-override-open-class="open-kapa-widget"
data-modal-ask-ai-input-placeholder="Ask me a question about Docker…"
data-modal-title-color="#fff"
data-project-color="#1d63ed"
data-project-logo="/assets/images/logo-icon-white.svg"
data-project-name="Docker"
data-user-analytics-fingerprint-enabled="true"
data-website-id="{{ site.Params.kapa.id }}"
></script>
{{/* preload Roboto Flex as it's a critical font: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload */}}
<link href="/assets/fonts/RobotoFlex.woff2" rel="preload" as="font" type="font/woff2" crossorigin />
{{ partialCached "utils/css.html" "-" }}

View File

@ -1,6 +1,6 @@
<header
class="sticky top-0 z-20 h-16 px-4 text-white bg-gradient-to-r from-accent-light to-blue-light-500 dark:from-accent-dark dark:to-blue-dark-100">
<div class="mx-auto flex h-full max-w-[1400px] items-center justify-between">
<div class="mx-auto flex gap-4 h-full max-w-[1400px] items-center justify-between">
<div class="flex h-full items-center md:gap-8 gap-2">
{{ if not .IsHome }}
<button x-data @click="$dispatch('togglesidebar')"
@ -19,15 +19,20 @@
<nav>
<ul class="mt-1 box-content hidden gap-4 md:flex">
{{ range site.Menus.main }}
<li {{- if or (eq page .Page) (page.IsDescendant .Page) }} class="border-b-4" {{- end }}>
<li
{{- if or (eq page .Page) (page.IsDescendant .Page) }}
class="border-b-4"
{{- else }}
class="border-b-4 border-transparent hover:border-white/20"
{{- end }}>
<a class="block px-2 py-1" href="{{ .URL }}">{{ .Name }}</a>
</li>
{{ end }}
</ul>
</nav>
</div>
<div class="flex items-center gap-6">
{{ partialCached "search.html" "-" }}
<div class="flex items-center gap-6 flex-grow justify-end">
{{ partialCached "search-bar.html" "-" }}
<button aria-label="Theme switch" id="theme-switch" class="svg-icon"
x-data="{ theme: localStorage.getItem('theme-preference') }" x-init="$watch('theme', value => {
localStorage.setItem('theme-preference', value);

View File

@ -0,0 +1,124 @@
<!-- search button, mobile (link off to the search page for now) -->
<a href="/search" class="sm:hidden">
<span class="icon-svg">{{ partialCached "icon" "search" "search" }}</span>
</a>
<!-- search button -->
<div x-ref="searchBarRef" x-data="{ open: false, focus: false }" @focus="open = true;" @click.outside="open = false;"
@keyup.escape.window="open = false" id="search-bar"
class="hidden sm:flex relative bg-white/10 rounded items-center p-2 sm:w-full xl:w-[400px]">
{{ (resources.Get "images/search-ai.svg").Content | safeHTML }}
<input x-ref="searchBarInput" type="search" id="search-bar-input" @click="open = true" @focus="open = true;"
@blur.window.capture="$refs.searchBarRef.contains($event.relatedTarget) || (open = false);"
@keyup.enter.prevent="window.location.href = '/search?q=' + $event.target.value;"
@keyup.escape.prevent="open = false;" @keydown.window="(e) => {
switch(e.key) {
case 'k':
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
$el.focus();
open = true;
}
break;
}
}" class="flex-grow px-2 bg-transparent text-white placeholder:text-white outline-none" placeholder="Search"
tabindex="0" />
<div x-cloak :class="open && 'hidden'" class="border px-1 text-sm border-white rounded flex items-center">
<div class="-mt-0.5">
<span x-show="navigator.platform == 'MacIntel'" class="icon-svg icon-sm">{{ partialCached "icon" "keyboard_command_key" "keyboard_command_key" }}</span>
<span x-show="navigator.platform != 'MacIntel'" class="icon-svg icon-sm">{{ partialCached "icon" "keyboard_control_key" "keyboard_control_key" }}</span>
</div>
<span>K</span>
</div>
<div x-cloak :class="open || 'hidden'">
<button tabindex="-1" @click="$refs.searchBarInput.value = ''; open = false" class="text-white hover:text-white">
<span class="icon-svg">{{ partialCached "icon" "close" "close" }}</span>
</button>
</div>
<div x-show="open" x-cloak
class="absolute px-6 py-4 right-0 w-screen max-w-xl top-full bg-background-light dark:bg-background-dark rounded shadow-lg z-50">
<button @click="open = false"
class="text-black dark:text-white py-1 px-2 rounded w-full open-kapa-widget flex justify-between items-center hover:bg-gray-light-200 dark:hover:bg-gray-dark-200">
<div class="flex gap-1 items-center">
<img src="{{ (resources.Get "images/ai-stars.svg").Permalink }}" alt="AI Stars" />
<span>Ask AI</span>
</div>
<span></span>
</button>
<hr class="mt-2 border-b border-divider-light dark:border-divider-dark">
<div id="search-bar-results">
<div class="p-2 text-gray-light dark:text-gray-dark">Start typing to search…</div>
<!-- results -->
</div>
</div>
<script type="module">
window.addEventListener("load", async function () {
const pagefind = await import("/pagefind/pagefind.js");
const searchBarInput = document.querySelector("#search-bar-input");
const searchBarResults = document.querySelector(
"#search-bar-results",
);
async function search(e) {
const query = e.target.value;
if (query === "") {
searchBarResults.innerHTML = `<div class="p-2 text-gray-light dark:text-gray-dark">Start typing to search…</div>`;
return;
}
const search = await pagefind.debouncedSearch(query);
if (search === null) {
return;
} else {
const resultsLength = search.results.length
const resultsData = await Promise.all(search.results.slice(0, 5).map(r => r.data()));
const results = resultsData.map((item, index) => ({...item, index: index + 1}));
if (query) {
searchBarResults.classList.remove("hidden");
} else {
searchBarResults.classList.add("hidden");
}
let resultsHTML = `<div class="p-2 text-gray-light dark:text-gray-dark">${resultsLength} results</div>`;
resultsHTML += results
.map((item) => {
return `<div class="p-2">
<div class="flex flex-col">
<a class="link" href="${item.url}" data-query="${query}" data-index="${item.index}">${item.meta.title}</a>
<p class="text-black dark:text-white overflow-hidden">…${item.excerpt}…</p>
</div>
</div>`;
})
.join("");
if (resultsLength > 5) {
resultsHTML += `<div class="w-fit ml-auto px-4 py-2"><a href="/search?q=${query}" class="link">Show all results</a></div>`;
}
searchBarResults.innerHTML = resultsHTML;
}
}
searchBarInput.addEventListener("input", search);
// Event delegation for tracking link clicks
if (window.heap !== undefined) {
searchBarResults.addEventListener('click', function (event) {
if (event.target.tagName === 'A' && event.target.closest('.link')) {
const searchQuery = event.target.getAttribute('data-query');
const resultIndex = event.target.getAttribute('data-index');
const url = new URL(event.target.href);
const properties = {
docs_search_target_path: url.pathname,
docs_search_target_title: event.target.textContent,
docs_search_query_text: searchQuery,
docs_search_target_index: resultIndex,
docs_search_source_path: window.location.pathname,
docs_search_source_title: document.title,
};
heap.track("Docs - Search - Click - Result Link", properties);
}
});
}
});
</script>
</div>

View File

@ -1,112 +0,0 @@
<div x-data="{ open: false }"
@keyup.escape.window="open = false; if (!open) { document.body.classList.remove('overflow-hidden'); }"
@keydown.window="(e) => {
switch(e.key) {
case 'k':
if (e.metaKey || e.ctrlKey) {
e.preventDefault()
open = !open;
if (open) {
document.body.classList.add('overflow-hidden');
} else {
document.body.classList.remove('overflow-hidden');
}
}
}
}">
<!-- search button -->
<button @click="open = true; document.body.classList.add('overflow-hidden');">
<span class="icon-svg">{{ partialCached "icon" "search" "search" }}</span>
</button>
<!-- search modal -->
<div class="fixed left-0 top-0 z-20 flex w-lvw justify-center py-12 text-gray-light-800 dark:text-gray-dark-800"
role="dialog" tabindex="-1" x-show="open" x-trap="open" x-cloak x-transition>
<div
class="mx-8 flex max-h-[80vh] w-full flex-col overflow-hidden rounded-lg bg-white p-2 dark:bg-background-dark lg:w-[600px] xl:w-[800px]"
@click.away="open = false; document.body.classList.remove('overflow-hidden');">
<div class="m-2 text-xl">Search Docker documentation</div>
<header class="flex items-center py-2">
<input type="search" id="modal-search-input"
class="mx-1 flex h-12 flex-auto appearance-none rounded bg-transparent p-4 outline outline-2 outline-gray-light focus:outline-blue-light dark:outline-gray-dark dark:focus:outline-blue-dark"
placeholder="Search..." tabindex="0" />
<div class="icon-svg px-2">
{{ partialCached "icon" "search" "search" }}
</div>
</header>
<section class="overflow-y-auto px-2">
<div class="flex min-h-0 flex-col gap-2" id="modal-search-results">
<!-- results -->
</div>
</section>
</div>
</div>
<!-- search modal backdrop -->
<div class="fixed left-0 top-0 h-full w-full bg-background-dark/70 dark:bg-gray-dark-100/70" x-show="open" x-cloak>
</div>
<script type="module">
window.addEventListener("load", async function () {
const pagefind = await import("/pagefind/pagefind.js");
await pagefind.options({
ranking: {
termFrequency: 0.2,
pageLength: 0.75,
termSaturation: 1.4,
termSimilarity: 6.0,
},
});
pagefind.init();
const modalSearchInput = document.querySelector("#modal-search-input");
const modalSearchResults = document.querySelector(
"#modal-search-results",
);
async function modalSearch(e) {
const query = e.target.value;
const search = await pagefind.debouncedSearch(query);
if (search === null) {
return;
} else {
const resultsData = await Promise.all(search.results.map(r => r.data()));
const results = resultsData.map((item, index) => ({...item, index: index + 1}));
let resultsHTML = `<div>${results.length} results</div>`;
resultsHTML += results
.map((item) => {
return `<div class="bg-gray-light-100 dark:bg-gray-dark-200 rounded p-4">
<div class="flex flex-col">
<a class="link" href="${item.url}" data-query="${query}" data-index="${item.index}">${item.meta.title}</a>
<p class="text-gray-light dark:text-gray-dark overflow-hidden">…${item.excerpt}…</p>
</div>
</div>`;
})
.join("");
modalSearchResults.innerHTML = resultsHTML;
}
}
modalSearchInput.addEventListener("input", modalSearch);
// Event delegation for tracking link clicks
if (window.heap !== undefined) {
modalSearchResults.addEventListener('click', function(event) {
if (event.target.tagName === 'A' && event.target.closest('.link')) {
const searchQuery = event.target.getAttribute('data-query');
const resultIndex = event.target.getAttribute('data-index');
const url = new URL(event.target.href);
const properties = {
docs_search_target_path: url.pathname,
docs_search_target_title: event.target.textContent,
docs_search_query_text: searchQuery,
docs_search_target_index: resultIndex,
docs_search_source_path: window.location.pathname,
docs_search_source_title: document.title,
};
heap.track("Docs - Search - Click - Result Link", properties);
}
});
}
});
</script>
</div>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB