85 lines
1.9 KiB
Vue
85 lines
1.9 KiB
Vue
<script setup lang="ts">
|
|
import type { WebNovel } from '~/types'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const open = ref(false)
|
|
const query = ref('')
|
|
const results = ref<WebNovel[]>([])
|
|
const isLoading = ref(false)
|
|
|
|
const searchNovels = async () => {
|
|
if (!query.value.trim()) {
|
|
results.value = []
|
|
return
|
|
}
|
|
|
|
isLoading.value = true
|
|
try {
|
|
const response = await $fetch<WebNovel[]>('/api/novels/search', {
|
|
query: { q: query.value }
|
|
})
|
|
results.value = response
|
|
} catch (error) {
|
|
console.error('Search error:', error)
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const selectResult = (novel: WebNovel) => {
|
|
router.push(`/novels/${novel.id}`)
|
|
open.value = false
|
|
query.value = ''
|
|
}
|
|
|
|
// Watch for Ctrl+K
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
e.preventDefault()
|
|
open.value = !open.value
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('keydown', handleKeyDown)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('keydown', handleKeyDown)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<UCommandPalette
|
|
v-model="open"
|
|
:groups="[
|
|
{
|
|
id: 'novels',
|
|
label: 'Novels',
|
|
commands: results.map(novel => ({
|
|
id: novel.id,
|
|
label: `${novel.title}`,
|
|
description: `by ${typeof novel.author === 'string' ? novel.author : novel.author?.name} · ${(novel.rating || 0).toFixed(1)}⭐`,
|
|
icon: 'i-lucide-book',
|
|
click: () => selectResult(novel)
|
|
}))
|
|
}
|
|
]"
|
|
:loading="isLoading"
|
|
icon="i-lucide-search"
|
|
placeholder="Search novels..."
|
|
:ui="{ base: 'h-64 sm:h-96' }"
|
|
@update:model-value="val => open = val"
|
|
>
|
|
<template #default="{ query: q }">
|
|
<input
|
|
v-model="query"
|
|
placeholder="Search novels, authors, tags..."
|
|
class="w-full bg-transparent px-2 py-1 outline-none"
|
|
@input="searchNovels"
|
|
>
|
|
</template>
|
|
</UCommandPalette>
|
|
</template>
|