Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add search components & enhancements #467

Merged
merged 3 commits into from
Nov 8, 2024
Merged
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
61 changes: 7 additions & 54 deletions packages/valaxy-theme-yun/components/YunFuseSearch.vue
Original file line number Diff line number Diff line change
@@ -1,70 +1,23 @@
<script lang="ts" setup>
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import { useFuse } from '@vueuse/integrations/useFuse'
import { computed, ref, watch } from 'vue'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSiteConfig } from 'valaxy'
import type { FuseListItem } from 'valaxy/types'
import { useFuseSearch } from 'valaxy'
import { isClient, onClickOutside, useScrollLock } from '@vueuse/core'
const props = defineProps<{
defineProps<{
open: boolean
}>()
const emit = defineEmits(['close'])
const searchContainer = ref<HTMLElement>()
const isLocked = useScrollLock(isClient ? document.body : null)
const { t } = useI18n()
const fuseListData = ref<FuseListItem[]>([])
const siteConfig = useSiteConfig()
const keys = computed(() => {
const ks = siteConfig.value.fuse.options.keys || []
return ks.length === 0
? ['title', 'tags', 'categories', 'excerpt']
: ks
})
const input = ref('')
// todo export options
const useFuseOptions = computed<UseFuseOptions<FuseListItem>>(() => ({
fuseOptions: {
includeMatches: true,
findAllMatches: true,
...siteConfig.value.fuse.options,
keys: keys.value,
// threshold: 0.99,
// ignoreLocation: true,
},
// resultLimit: resultLimit.value,
// matchAllWhenSearchEmpty: matchAllWhenSearchEmpty.value,
}))
const { results } = useFuse(input, fuseListData, useFuseOptions)
const isLocked = useScrollLock(isClient ? document.documentElement : null)
const { t } = useI18n()
const { results } = useFuseSearch(input)
const searchInputRef = ref<HTMLInputElement>()
watch(() => props.open, async () => {
if (!props.open)
return
const fuseListDataPath = siteConfig.value.fuse.dataPath.startsWith('http')
? siteConfig.value.fuse.dataPath
: `${import.meta.env.BASE_URL}${siteConfig.value.fuse.dataPath}`
fetch(fuseListDataPath)
.then(res => res.json())
.then((data) => {
if (Array.isArray(data))
fuseListData.value = data
searchInputRef.value?.focus()
})
})
const searchContainer = ref<HTMLElement>()
onClickOutside(searchInputRef, () => {
// emit('close')
Expand Down
3 changes: 2 additions & 1 deletion packages/valaxy-theme-yun/components/YunSearchBtn.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ function onClick() {
<template>
<button
class="yun-search-btn popup-trigger size-12 inline-flex justify-center items-center"
:class="!open && 'hover-bg-white/80 hover:bg-black/80'"
text="xl $va-c-text"
hover="bg-white/80 dark:bg-black/80"
:title="t('menu.search')"
@click="onClick"
>
Expand All @@ -35,5 +35,6 @@ function onClick() {
<style lang="scss">
.yun-search-btn {
z-index: var(--yun-z-search-btn);
transition: background-color var(--va-transition-duration);
}
</style>
13 changes: 2 additions & 11 deletions packages/valaxy-theme-yun/components/YunSearchTrigger.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { useSiteConfig } from 'valaxy'
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
import { useEventListener } from '@vueuse/core'
import { useHotKey } from '../composables'
const siteConfig = useSiteConfig()
Expand All @@ -14,20 +14,11 @@ function togglePopup() {
open.value = !open.value
}
function handleSearchHotKey(event: KeyboardEvent) {
if (
(event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey))
) {
event.preventDefault()
togglePopup()
}
}
const algoliaRef = ref()
onMounted(() => {
// algolia has its own hotkey
if (isFuse.value)
useEventListener('keydown', handleSearchHotKey)
useHotKey('k', togglePopup)
})
function openSearch() {
Expand Down
17 changes: 16 additions & 1 deletion packages/valaxy-theme-yun/composables/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ref, watch } from 'vue'
import { isClient } from '@vueuse/core'
import { isClient, useEventListener } from '@vueuse/core'

/**
* fetch data from source, and random
Expand All @@ -25,3 +25,18 @@ export function useRandomData<T>(source: string | T[], random = false) {
data,
}
}

export function useHotKey(key: string, callback: () => void) {
const isHotKeyActive = ref(false)

function handleHotKey(event: KeyboardEvent) {
if (event.key.toLowerCase() === key.toLowerCase() && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
callback()
}
}

useEventListener('keydown', handleHotKey)

return { isHotKeyActive }
}
1 change: 1 addition & 0 deletions packages/valaxy/client/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './outline'
// utils
export * from './back'
export * from './decrypt'
export * from './search'

// app
export * from './app'
57 changes: 57 additions & 0 deletions packages/valaxy/client/composables/search/fuse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { computed, onMounted, shallowRef } from 'vue'
import { useSiteConfig } from 'valaxy'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import { useFuse } from '@vueuse/integrations/useFuse'
import type { FuseListItem } from 'valaxy/types'

export function useFuseSearch<T extends FuseListItem = FuseListItem>(
search: MaybeRefOrGetter<string>,
options?: MaybeRefOrGetter<UseFuseOptions<T>>,
) {
const siteConfig = useSiteConfig()

const fuseListData = shallowRef<T[]>([])

const keys = computed(() => {
const ks = siteConfig.value.fuse.options.keys || []
return ks.length === 0 ? ['title', 'tags', 'categories', 'excerpt'] : ks
})

const defaultOptions: UseFuseOptions<T> = {
fuseOptions: {
includeMatches: true,
findAllMatches: true,
// threshold: 0.99,
// ignoreLocation: true,

...siteConfig.value.fuse.options,
keys: keys.value,
},
// resultLimit: resultLimit.value,
// matchAllWhenSearchEmpty: matchAllWhenSearchEmpty.value,
}
const useFuseOptions = computed<UseFuseOptions<T>>(() => ({
...defaultOptions,
...options,
}))

const ruse = useFuse<T>(search, fuseListData, useFuseOptions)

async function fetchFuseListData(path?: string) {
const fuseListDataPath = path
|| (siteConfig.value.fuse.dataPath.startsWith('http')
? siteConfig.value.fuse.dataPath
: `${import.meta.env.BASE_URL}${siteConfig.value.fuse.dataPath}`)

const res = await fetch(fuseListDataPath)
const data = await res.json()

if (Array.isArray(data))
fuseListData.value = data
}

onMounted(fetchFuseListData)

return ruse
}
1 change: 1 addition & 0 deletions packages/valaxy/client/composables/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './fuse'
23 changes: 13 additions & 10 deletions packages/valaxy/node/modules/fuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import matter from 'gray-matter'
import { cyan, dim } from 'picocolors'
import type { Argv } from 'yargs'

import type { FuseListItem } from 'valaxy/types'
import type { FuseListItem, PostFrontMatter } from 'valaxy/types'
import { matterOptions } from '../plugins/markdown/transform/matter'
import { resolveOptions } from '../options'
import { setEnvProd } from '../utils/env'
Expand All @@ -27,20 +27,21 @@ export async function generateFuseList(options: ResolvedValaxyOptions) {
for await (const i of files) {
const raw = fs.readFileSync(i, 'utf-8')
const { data, excerpt, content } = matter(raw, matterOptions)
const fmData = data as PostFrontMatter

if (data.draft) {
if (fmData.draft) {
consola.warn(`Ignore draft post: ${dim(i)}`)
continue
}

if (data.hide)
if (fmData.hide)
continue

// skip encrypt post
if (data.password)
if (fmData.password)
continue

const keys = options.config.siteConfig.fuse.options.keys || []
const extendKeys = options.config.fuse?.extendKeys || []

// adapt for nested folders, like /posts/2021/01/01/index.md
const relativeLink = i.replace(`${options.userRoot}/pages`, '')
Expand All @@ -49,16 +50,18 @@ export async function generateFuseList(options: ResolvedValaxyOptions) {
: relativeLink.replace(/\.md$/, '')

const fuseListItem: FuseListItem = {
title: data.title || '',
tags: (typeof data.tags === 'string' ? [data.tags] : data.tags) || [],
categories: data.categories || [],
title: fmData.title || '',
tags: (typeof fmData.tags === 'string' ? [fmData.tags] : fmData.tags) || [],
categories: (typeof fmData.categories === 'string' ? [fmData.categories] : fmData.categories) || [],
author: options.config.siteConfig.author.name,
excerpt: excerpt || content.slice(0, 100),
// encode for chinese url
link: encodeURI(link),
}
if (keys.includes('content'))
fuseListItem.content = content || ''

extendKeys.forEach((key) => {
fuseListItem[key] = fmData[key] || ''
})

posts.push(fuseListItem)
}
Expand Down
10 changes: 10 additions & 0 deletions packages/valaxy/node/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ export interface ValaxyExtendConfig {
icons?: Parameters<typeof presetIcons>[0]
typography?: Parameters<typeof presetTypography>[0]
}
fuse?: {
/**
* @en_US Extends the metadata fields returned by the search
* @zh_CN 扩展搜索返回的元数据字段
* @default []
* @description:en-US By default, returns the following fields: title, tags, categories, author, excerpt, link
* @description:zh-CN 默认返回以下字段:title、tags、categories、author、excerpt、link
*/
extendKeys?: string[]
}
/**
* @experimental
* Enable Vue Devtools & Valaxy Devtools
Expand Down
5 changes: 4 additions & 1 deletion packages/valaxy/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ export interface SiteConfig {
*/
options: FuseOptions<FuseListItem> & {
/**
* @en_US The fields to be searched.
* @zh_CN 搜索的字段
* @default ['title', 'tags', 'categories', 'excerpt']
* @description 搜索的字段
* @description:en-US List of keys that will be searched. This supports nested paths, weighted search, and searching in arrays of strings and objects
* @description:zh-CN 搜索将会涉及的字段列表,支持嵌套路径、加权搜索以及在字符串和对象数组中进行搜索
* @see https://fusejs.io/api/options.html#keys
*/
keys: FuseOptions<FuseListItem>['keys']
Expand Down
2 changes: 1 addition & 1 deletion packages/valaxy/types/node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface FuseListItem {
export interface FuseListItem extends Record<string, any> {
title: string
excerpt?: string
author: string
Expand Down
Loading