Skip to content
Closed
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
121 changes: 111 additions & 10 deletions _components/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,124 @@
export default function SearchInput() {
return (
<>
<form
action="https://www.google.com/search"
method="get"
className="mb-0"
target="_blank"
>
<div className="relative">
<input
type="search"
name="q"
id="search-str"
id="pagefind-search"
placeholder="Search"
className="w-full min-w-24 rounded-lg text-sm leading-normal pt-1 pr-3 pb-1 pl-8 border transition-all duration-150
text-foreground-primary border-foreground-tertiary hover:bg-background-secondary focus:bg-background-secondary"
style="background: url(/img/search.svg) no-repeat 0.5em 50%; background-size: 1em; background-color: var(--color-foreground-quaternary);"
/>
<input type="hidden" name="q" id="q" value="site:deno.com" />
</form>
<div
id="pagefind-results"
className="absolute top-full right-0 z-50 w-96 bg-background-primary border border-foreground-tertiary rounded-md shadow-lg max-h-96 overflow-y-auto hidden"
>
</div>
</div>
<script
dangerouslySetInnerHTML={{
__html: `
document.addEventListener('DOMContentLoaded', async () => {
const searchInput = document.getElementById('pagefind-search');
const resultsContainer = document.getElementById('pagefind-results');

if (!searchInput || !resultsContainer) return;

let pagefind;
try {
pagefind = await import('/pagefind/pagefind.js');
} catch (e) {
console.warn('Pagefind not found, search functionality disabled');
return;
}

let currentSearch = null;

const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};

const performSearch = async (query) => {
if (!query.trim()) {
resultsContainer.innerHTML = '';
resultsContainer.classList.add('hidden');
return;
}

try {
currentSearch = query;
const search = await pagefind.search(query);

// Check if this is still the current search
if (currentSearch !== query) return;

if (search.results.length === 0) {
resultsContainer.innerHTML = '<div class="p-4 text-foreground-secondary">No results found</div>';
resultsContainer.classList.remove('hidden');
return;
}

// Load first 5 results
const results = await Promise.all(
search.results.slice(0, 5).map(r => r.data())
);

// Check again if this is still the current search
if (currentSearch !== query) return;

resultsContainer.innerHTML = results.map(result => \`
<a href="\${result.url}" class="block p-3 hover:bg-background-secondary border-b border-foreground-quaternary last:border-b-0">
<div class="font-medium text-foreground-primary text-sm">\${result.meta.title || 'Untitled'}</div>
<div class="text-xs text-foreground-secondary mt-1 line-clamp-2">\${result.excerpt}</div>
</a>
\`).join('');

resultsContainer.classList.remove('hidden');
} catch (error) {
console.error('Search error:', error);
}
};

const debouncedSearch = debounce(performSearch, 300);

searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});

searchInput.addEventListener('focus', async () => {
try {
await pagefind.init();
} catch (e) {
// Silently fail if pagefind isn't available
}
});

// Hide results when clicking outside
document.addEventListener('click', (e) => {
if (!searchInput.contains(e.target) && !resultsContainer.contains(e.target)) {
resultsContainer.classList.add('hidden');
}
});

// Show results when clicking on search input if there's content
searchInput.addEventListener('click', () => {
if (resultsContainer.innerHTML && searchInput.value.trim()) {
resultsContainer.classList.remove('hidden');
}
});
});
`,
}}
/>
</>
);
}
37 changes: 24 additions & 13 deletions _includes/doc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,25 @@ export default function Doc(data: Lume.Data, helpers: Lume.Helpers) {
<div class="w-full">
<article class="mx-auto">
{hasBreadcrumbs && (
<data.comp.Breadcrumbs
title={data.title!}
sidebar={sidebar}
url={data.url}
/>
<div data-pagefind-ignore>
<data.comp.Breadcrumbs
title={data.title!}
sidebar={sidebar}
url={data.url}
/>
</div>
)}

<data.comp.TableOfContentsMobile toc={data.toc} data={data} />
<div data-pagefind-ignore>
<data.comp.TableOfContentsMobile toc={data.toc} data={data} />
</div>

<div
data-color-mode="auto"
data-light-theme="light"
data-dark-theme="dark"
class="markdown-body mt-4 sm:mt-6"
data-pagefind-meta="title[content]"
>
{!isReference && (
<h1
Expand All @@ -69,16 +74,22 @@ export default function Doc(data: Lume.Data, helpers: Lume.Helpers) {
{data.children}
</div>
</article>
{!isReference && <data.comp.Feedback file={file} />}
{!isReference && (
<div data-pagefind-ignore>
<data.comp.Feedback file={file} />
</div>
)}
</div>

{(isReference && data.children.props.data.toc_ctx) && (
<data.comp.RefToc
documentNavigation={data.children.props.data.toc_ctx
.document_navigation}
documentNavigationStr={data.children.props.data.toc_ctx
.document_navigation_str}
/>
<div data-pagefind-ignore>
<data.comp.RefToc
documentNavigation={data.children.props.data.toc_ctx
.document_navigation}
documentNavigationStr={data.children.props.data.toc_ctx
.document_navigation_str}
/>
</div>
)}
</main>
);
Expand Down
25 changes: 15 additions & 10 deletions _includes/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,25 @@ export default function Layout(data: Lume.Data) {
class={`layout ${
data.toc?.length ? "layout--three-column" : "layout--two-column"
}`}
data-pagefind-body
>
<data.comp.Navigation
data={data}
currentSection={section}
currentUrl={data.url}
hasSubNav={hasSubNav}
/>
{data.children}
{!isReference && (
<data.comp.TableOfContents
toc={data.toc}
<div data-pagefind-ignore>
<data.comp.Navigation
data={data}
currentSection={section}
currentUrl={data.url}
hasSubNav={hasSubNav}
/>
</div>
{data.children}
{!isReference && (
<div data-pagefind-ignore>
<data.comp.TableOfContents
toc={data.toc}
data={data}
hasSubNav={hasSubNav}
/>
</div>
)}
</div>
<data.comp.Footer />
Expand Down
6 changes: 5 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@
"dev": "deno task serve:no_logs",
"build": {
"description": "Full production build by default",
"command": "deno task build:prod && deno task tw"
"command": "deno task build:prod && deno task tw && deno task pagefind"
},
"build:prod": {
"description": "Build the entire site including expensive operations",
"command": "BUILD_TYPE=FULL deno run --env-file -A lume.ts"
},
"tw": "tailwindcss -i styles.css -o _site/styles.css --minify",
"pagefind": {
"description": "Index the site with Pagefind for search functionality",
"command": "npx -y pagefind --site _site"
},
"build:light": {
"description": "Build the site without expensive operations",
"command": "deno run --env-file -A lume.ts"
Expand Down
2 changes: 1 addition & 1 deletion styleguide/components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: "The components available to use across the Deno documentation."

## Search input

Form element for searching the deno domain with google search
Form element for searching the site content with Pagefind

```tsx
<comp.SearchInput />
Expand Down
Loading