Skip to content

Commit

Permalink
feat: global search
Browse files Browse the repository at this point in the history
  • Loading branch information
FjellOverflow committed Oct 25, 2024
1 parent cdcbf5e commit dd2d6be
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 4 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"check": "pnpm lint && astro check",
"build": "astro build",
"preview": "pnpm build && astro preview",
"release": "pnpm check && commit-and-tag-version --sign"
"release": "pnpm check && commit-and-tag-version --sign",
"postbuild": "pagefind --site dist"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
Expand All @@ -42,6 +43,7 @@
"astro": "^4.16.7",
"mdast-util-to-string": "^4.0.0",
"medium-zoom": "^1.1.0",
"pagefind": "^1.1.1",
"reading-time": "^1.5.0",
"satori": "^0.11.2",
"sharp": "^0.33.5",
Expand Down
55 changes: 55 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/components/layout/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,21 @@ export const isNavItem = (item: HeaderItem): item is NavItemType =>
</li>
))
}
<li class="text-2xl">
<a href="/search" class="flex items-center">
<span class="clickable iconify text-xl tabler--search"></span>
</a>
</li>
<li class="text-2xl">
{config.modeToggle && <ModeToggle />}
</li>
</ul>
</nav>
<div class="flex items-center justify-center gap-4 text-2xl sm:hidden">
{config.modeToggle && <ModeToggle />}
<a href="/search">
<span class="clickable iconify text-xl tabler--search"></span>
</a>
<MobileNavToggle />
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions src/layouts/PageLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type Props = BaseLayoutProps & {
scrollProgress: boolean
activeHeaderLink: string
scrollToTop: boolean
searchable: boolean
}>
}
Expand All @@ -22,7 +23,8 @@ const { frontmatter } = Astro.props
const {
scrollProgress = config.scrollProgress,
scrollToTop = config.scrollToTop,
activeHeaderLink
activeHeaderLink,
searchable = false
} = frontmatter
---

Expand All @@ -35,7 +37,7 @@ const {
<slot name="aside" />
</aside>

<main class="mt-6 sm:mt-12">
<main class="mt-6 sm:mt-12" data-pagefind-body={searchable}>
<Prose class="mb-6 sm:mb-12">
<slot />
</Prose>
Expand Down
3 changes: 2 additions & 1 deletion src/layouts/PostLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const frontmatter: PageLayoutProps['frontmatter'] = {
...post.data,
openGraphImage: post.data.openGraphImage || `/posts/${post.slug}.png`,
activeHeaderLink: 'Blog',
scrollProgress: true
scrollProgress: true,
searchable: true
}
const publishedStr = toDateString(post.data.publishedDate)
Expand Down
101 changes: 101 additions & 0 deletions src/pages/search.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
import PageLayout, {
type Props as PageLayoutProps
} from '@/layouts/PageLayout.astro'
const frontmatter: PageLayoutProps['frontmatter'] = {
title: 'Search',
activeHeaderLink: '/search'
}
---

<PageLayout {frontmatter}>
<h1>{frontmatter.title}</h1>
<search-input class="flex w-full items-center justify-center gap-8">
<input
id="search-input"
type="text"
placeholder="Type to search"
class="w-full rounded border border-accent p-4 sm:w-[50%]"
/>
<span id="search-clear" class="clickable invisible text-xl">Clear</span>
</search-input>
<h2 id="results-heading">{''}</h2>
<ul id="results-list" class="list-none p-0"></ul>
</PageLayout>

<script is:inline>
if (!customElements.get('search-input')) {
customElements.define(
'search-input',
class extends HTMLElement {
async connectedCallback() {
const searchInput = this.querySelector('#search-input')
const clearBtn = this.querySelector('#search-clear')
const resultsHeading = document.querySelector('#results-heading')
const resultsContainer = document.querySelector('#results-list')

if (!searchInput || !clearBtn || !resultsHeading || !resultsContainer)
return

const onInput = async () => {
const searchQuery = searchInput.value

if (searchQuery === '') clearBtn.classList.add('invisible')
else clearBtn.classList.remove('invisible')

const { results } = await window.pagefind.search(searchQuery)

resultsHeading.innerHTML =
results.length > 0 ? `Results (${results.length})` : 'No Results'

resultsContainer.innerHTML = ''

await Promise.all(
results.slice(0, 5).map(async (r) => {
const li = document.createElement('li')
li.innerHTML = await renderSearchResult(r)
resultsContainer.appendChild(li)
})
)

if (results.length > 5) {
const remainingLi = document.createElement('li')
remainingLi.innerHTML = `... and ${results.length - 5} more`
resultsContainer.appendChild(remainingLi)
}
}

const renderSearchResult = async (result) => {
const data = await result.data()

return `<a class="text-xl font-normal" href="${data.url}">
${data.meta.title}
</a>
<div class="opacity-75 mt-4">
${data.excerpt}
</div>`
}

searchInput.addEventListener('input', onInput)

clearBtn.addEventListener('click', () => {
searchInput.value = ''
onInput()
})

if (this.dataset.loaded !== 'true') {
try {
window.pagefind = await import('/pagefind/pagefind.js')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
console.log('Pagefind not available')
window.pagefind = { search: () => ({ results: [] }) }
}
this.dataset.loaded = 'true'
}
}
}
)
}
</script>

0 comments on commit dd2d6be

Please sign in to comment.