Skip to content

Commit

Permalink
feat(theme): introduce independent search page
Browse files Browse the repository at this point in the history
  • Loading branch information
SamJUK committed Nov 1, 2024
1 parent 2b3cd95 commit bfe2410
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 15 deletions.
13 changes: 11 additions & 2 deletions src/client/app/router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Component, InjectionKey } from 'vue'
import { inject, markRaw, nextTick, reactive, readonly } from 'vue'
import type { Awaitable, PageData, PageDataPayload } from '../shared'
import { notFoundPageData, treatAsHtml } from '../shared'
import { notFoundPageData, searchPageData, treatAsHtml } from '../shared'
import { siteDataRef } from './data'
import { getScrollOffset, inBrowser, withBase } from './utils'

Expand Down Expand Up @@ -58,7 +58,8 @@ interface PageModule {

export function createRouter(
loadPageModule: (path: string) => Awaitable<PageModule | null>,
fallbackComponent?: Component
fallbackComponent?: Component,
searchComponent?: Component
): Router {
const route = reactive(getDefaultRoute())

Expand All @@ -85,6 +86,14 @@ export function createRouter(
if ((await router.onBeforePageLoad?.(href)) === false) return
const targetLoc = new URL(href, fakeHost)
const pendingPath = (latestPendingPath = targetLoc.pathname)

if (pendingPath === '/search') {
route.path = inBrowser ? pendingPath : withBase(pendingPath)
route.component = searchComponent ? markRaw(searchComponent) : null
route.data = searchPageData
return
}

try {
let page = await loadPageModule(pendingPath)
if (!page) {
Expand Down
69 changes: 69 additions & 0 deletions src/client/theme-default/Search.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import {
defineAsyncComponent,
ref
} from 'vue'
import { useData } from './composables/data'
const { theme, site } = useData()
const VPLocalSearchBox = __VP_LOCAL_SEARCH__
? defineAsyncComponent(() => import('./components/VPLocalSearchBox.vue'))
: () => null
const VPAlgoliaSearchBox = __ALGOLIA__
? defineAsyncComponent(() => import('./components/VPAlgoliaSearchBox.vue'))
: () => null
const provider = __ALGOLIA__ ? 'algolia' : __VP_LOCAL_SEARCH__ ? 'local' : ''
const filterText = ref('');
const handleFilterTextChange = (text: string) => {
filterText.value = text;
const newUrl = (new URL(window.location.href));
if (text) {
newUrl.searchParams.set('q', text)
} else {
newUrl.searchParams.delete('q');
}
history.replaceState({}, '', newUrl.href)
}
const title = site.value.title;
</script>

<template>
<div class="Search">

<div class="search-wrapper">
<h1 class="title">{{ filterText ? `Search Results For "${filterText}"` : `Search ${title}` }}</h1>

<template v-if="provider === 'local'">
<VPLocalSearchBox @filter-change="handleFilterTextChange"/>
</template>

<template v-else-if="provider === 'algolia'">
<VPAlgoliaSearchBox
:algolia="theme.search?.options ?? theme.algolia"
/>
</template>
</div>

</div>
</template>

<style scoped>
.search-wrapper {
position: relative;
padding: 12px;
margin: 32px auto 0;
width: min(100vw - 60px, 900px);
}
.title {
margin-bottom: 12px;
letter-spacing: -0.02em;
line-height: 40px;
font-size: 32px;
font-weight: 600;
outline: none;
}
</style>
4 changes: 3 additions & 1 deletion src/client/theme-default/components/VPContent.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import NotFound from '../NotFound.vue'
import Search from '../Search.vue'
import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar'
import VPDoc from './VPDoc.vue'
Expand All @@ -19,7 +20,8 @@ const { hasSidebar } = useSidebar()
'is-home': frontmatter.layout === 'home'
}"
>
<slot name="not-found" v-if="page.isNotFound"><NotFound /></slot>
<slot name="search" v-if="page.isSearch"><Search /></slot>
<slot name="not-found" v-else-if="page.isNotFound"><NotFound /></slot>

<VPPage v-else-if="frontmatter.layout === 'page'">
<template #page-top><slot name="page-top" /></template>
Expand Down
32 changes: 23 additions & 9 deletions src/client/theme-default/components/VPLocalSearchBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import { LRUCache } from '../support/lru'
import { createSearchTranslate } from '../support/translation'
const emit = defineEmits<{
(e: 'close'): void
(e: 'close'): void,
(e: 'filter-change', text: string): void
}>()
const el = shallowRef<HTMLElement>()
Expand All @@ -60,6 +61,7 @@ interface Result {
}
const vitePressData = useData()
const isSearchPage = vitePressData.page.value.isSearch;
const { activate } = useFocusTrap(el, {
immediate: true,
allowOutsideClick: true,
Expand Down Expand Up @@ -135,6 +137,7 @@ const enableNoResults = ref(false)
watch(filterText, () => {
enableNoResults.value = false
emit('filter-change', filterText.value);
})
const mark = computedAsync(async () => {
Expand Down Expand Up @@ -270,6 +273,8 @@ function focusSearchInput(select = true) {
onMounted(() => {
focusSearchInput()
filterText.value = (new URL(window.location.href)).searchParams.get('q') ?? ''
emit('filter-change', filterText.value);
})
function onSearchBarClick(event: PointerEvent) {
Expand Down Expand Up @@ -377,7 +382,7 @@ const isLocked = useScrollLock(inBrowser ? document.body : null)
onMounted(() => {
nextTick(() => {
isLocked.value = true
isLocked.value = !isSearchPage
nextTick().then(() => activate())
})
})
Expand Down Expand Up @@ -413,15 +418,18 @@ function onMouseMove(e: MouseEvent) {
</script>

<template>
<Teleport to="body">
<Teleport to="body" :disabled="isSearchPage">
<div
ref="el"
role="button"
:aria-owns="results?.length ? 'localsearch-list' : undefined"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="localsearch-label"
class="VPLocalSearchBox"
:class="{
'VPLocalSearchBox': true,
'overlay': !isSearchPage
}"
>
<div class="backdrop" @click="$emit('close')" />

Expand Down Expand Up @@ -569,7 +577,7 @@ function onMouseMove(e: MouseEvent) {
</kbd>
{{ translate('modal.footer.selectText') }}
</span>
<span>
<span v-if="!isSearchPage">
<kbd :aria-label="translate('modal.footer.closeKeyAriaLabel')">esc</kbd>
{{ translate('modal.footer.closeText') }}
</span>
Expand All @@ -580,32 +588,38 @@ function onMouseMove(e: MouseEvent) {
</template>

<style scoped>
.VPLocalSearchBox {
.VPLocalSearchBox.overlay {
position: fixed;
z-index: 100;
inset: 0;
display: flex;
}
.backdrop {
.VPLocalSearchBox.overlay .backdrop {
position: absolute;
inset: 0;
background: var(--vp-backdrop-bg-color);
transition: opacity 0.5s;
}
.VPLocalSearchBox.overlay .shell {
margin-top: 64px;
max-height: min(100vh - 128px, 900px);
z-index: unset;
}
.shell {
position: relative;
padding: 12px;
margin: 64px auto;
margin: 0 auto 64px;
display: flex;
flex-direction: column;
gap: 16px;
background: var(--vp-local-search-bg);
width: min(100vw - 60px, 900px);
height: min-content;
max-height: min(100vh - 128px, 900px);
border-radius: 6px;
z-index: 0;
}
@media (max-width: 767px) {
Expand Down
6 changes: 4 additions & 2 deletions src/client/theme-default/components/VPNavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ defineEmits<{
const { y } = useWindowScroll()
const { hasSidebar } = useSidebar()
const { frontmatter } = useData()
const { frontmatter, page } = useData()
const isSearchPage = page.value.isSearch
const classes = ref<Record<string, boolean>>({})
Expand Down Expand Up @@ -50,7 +52,7 @@ watchPostEffect(() => {
<div class="content">
<div class="content-body">
<slot name="nav-bar-content-before" />
<VPNavBarSearch class="search" />
<VPNavBarSearch class="search" v-if="!isSearchPage"/>
<VPNavBarMenu class="menu" />
<VPNavBarTranslations class="translations" />
<VPNavBarAppearance class="appearance" />
Expand Down
15 changes: 14 additions & 1 deletion src/shared/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,20 @@ export const notFoundPageData: PageData = {
headers: [],
frontmatter: { sidebar: false, layout: 'page' },
lastUpdated: 0,
isNotFound: true
isNotFound: true,
isSearch: false
}

export const searchPageData: PageData = {
relativePath: 'search.md',
filePath: '',
title: 'Search',
description: 'Search Page',
headers: [],
frontmatter: { sidebar: false, layout: 'page' },
lastUpdated: 0,
isNotFound: false,
isSearch: true
}

export function isActive(
Expand Down
1 change: 1 addition & 0 deletions types/shared.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface PageData {
frontmatter: Record<string, any>
params?: Record<string, any>
isNotFound?: boolean
isSearch?: boolean
lastUpdated?: number
}

Expand Down

0 comments on commit bfe2410

Please sign in to comment.