feat: support tags search (#68)

This commit is contained in:
woolen-sheep
2025-07-20 18:42:43 +08:00
committed by GitHub
parent 2f479fe176
commit 2df572e7e8
9 changed files with 105 additions and 5 deletions

View File

@@ -52,4 +52,4 @@
"exportall.config.folderListener": [
"/apps/ssr/src/schemas"
]
}
}

View File

@@ -7,6 +7,8 @@ export const gallerySettingAtom = atom({
sortBy: 'date' as GallerySortBy,
sortOrder: 'desc' as GallerySortOrder,
selectedTags: [] as string[],
tagSearchQuery: '' as string,
isTagsPanelOpen: false as boolean,
columns: 'auto' as number | 'auto', // 自定义列数auto 表示自动计算
})

View File

@@ -1,6 +1,6 @@
import { photoLoader } from '@afilmory/data'
import { useAtom } from 'jotai'
import { useState } from 'react'
import { useAtom, useSetAtom } from 'jotai'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import { Drawer } from 'vaul'
@@ -65,6 +65,15 @@ const SortPanel = () => {
const TagsPanel = () => {
const { t } = useTranslation()
const [gallerySetting, setGallerySetting] = useAtom(gallerySettingAtom)
const [searchQuery, setSearchQuery] = useState(gallerySetting.tagSearchQuery)
const inputRef = useRef<HTMLInputElement>(null)
// 当面板打开时自动聚焦输入框
useEffect(() => {
if (gallerySetting.isTagsPanelOpen && inputRef.current) {
inputRef.current?.focus()
}
}, [gallerySetting.isTagsPanelOpen])
const toggleTag = (tag: string) => {
const newSelectedTags = gallerySetting.selectedTags.includes(tag)
@@ -81,9 +90,33 @@ const TagsPanel = () => {
setGallerySetting({
...gallerySetting,
selectedTags: [],
tagSearchQuery: '', // 清除搜索查询
isTagsPanelOpen: false, // 关闭标签面板
})
}
const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const query = e.target.value
setSearchQuery(query)
setGallerySetting((prev) => ({
...prev,
tagSearchQuery: query, // 同步搜索查询
}))
}
// 根据正则查询过滤标签
const filteredTags = allTags.filter((tag) => {
if (!searchQuery) return true
try {
const regex = new RegExp(searchQuery, 'i')
return regex.test(tag)
} catch {
// 如果正则表达式无效,回退到简单的包含查询
return tag.toLowerCase().includes(searchQuery.toLowerCase())
}
})
return (
<div className="lg:pb-safe-2 w-full p-2 pb-0 text-sm lg:w-64 lg:p-0">
<div className="relative mb-2">
@@ -101,14 +134,32 @@ const TagsPanel = () => {
</Button>
)}
</div>
{/* 搜索栏 */}
<div className="mb-3 px-2">
<div className="relative">
<input
ref={inputRef}
type="text"
placeholder={t('action.tag.search')}
value={searchQuery}
onChange={onSearchChange}
className="w-full rounded-md border border-gray-200 bg-transparent px-3 py-2 text-sm placeholder:text-gray-500 focus:border-gray-400 focus:outline-none dark:text-white dark:placeholder:text-gray-400 dark:focus:border-gray-500"
/>
<i className="i-mingcute-search-line absolute top-1/2 right-3 -translate-y-1/2 text-gray-400" />
</div>
</div>
{allTags.length === 0 ? (
<div className="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
{t('action.tag.empty')}
</div>
) : filteredTags.length === 0 ? (
<div className="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
{t('action.tag.not-found')}
</div>
) : (
<div className="pb-safe-offset-4 lg:pb-safe -mx-4 -mb-4 max-h-64 overflow-y-auto px-4 lg:mx-0 lg:mb-0 lg:px-0">
{allTags.map((tag) => (
{filteredTags.map((tag) => (
<div
key={tag}
onClick={() => toggleTag(tag)}
@@ -126,6 +177,16 @@ const TagsPanel = () => {
)
}
const onTagsPanelOpenChange = (
open: boolean,
setGallerySetting: (setting: any) => void,
) => {
setGallerySetting((prev) => ({
...prev,
isTagsPanelOpen: open,
}))
}
const ColumnsPanel = () => {
const { t } = useTranslation()
const [gallerySetting, setGallerySetting] = useAtom(gallerySettingAtom)
@@ -205,15 +266,28 @@ const DesktopActionButton = ({
badge,
children,
contentClassName,
open,
onOpenChange,
}: {
icon: string
title: string
badge?: number | string
children: React.ReactNode
contentClassName?: string
open?: boolean
onOpenChange?: (
open: boolean,
setGallerySetting: (setting: any) => void,
) => void
}) => {
const setGallerySetting = useSetAtom(gallerySettingAtom)
return (
<DropdownMenu>
<DropdownMenu
defaultOpen={open}
onOpenChange={(open) => {
onOpenChange?.(open, setGallerySetting)
}}
>
<DropdownMenuTrigger asChild>
<ActionButton
icon={icon}
@@ -273,12 +347,19 @@ const ResponsiveActionButton = ({
badge,
children,
contentClassName,
globalOpen,
onGlobalOpenChange,
}: {
icon: string
title: string
badge?: number | string
children: React.ReactNode
contentClassName?: string
globalOpen?: boolean
onGlobalOpenChange?: (
open: boolean,
setGallerySetting: (setting: any) => void,
) => void
}) => {
const isMobile = useMobile()
const [open, setOpen] = useState(false)
@@ -303,6 +384,8 @@ const ResponsiveActionButton = ({
title={title}
badge={badge}
contentClassName={contentClassName}
open={globalOpen}
onOpenChange={onGlobalOpenChange}
>
{children}
</DesktopActionButton>
@@ -336,6 +419,9 @@ export const ActionGroup = () => {
? gallerySetting.selectedTags.length
: undefined
}
// 使用全局状态实现滚动时自动收起标签面板
globalOpen={gallerySetting.isTagsPanelOpen}
onGlobalOpenChange={onTagsPanelOpenChange}
>
<TagsPanel />
</ResponsiveActionButton>

View File

@@ -8,6 +8,8 @@
"action.tag.clear": "Clear",
"action.tag.empty": "No tags available",
"action.tag.filter": "Tag Filter",
"action.tag.not-found": "No tags match your search",
"action.tag.search": "Search Tags",
"action.view.github": "View GitHub Repository",
"error.feedback": "Still having this issue? Please provide feedback on Github, thank you!",
"error.reload": "Reload",

View File

@@ -8,6 +8,8 @@
"action.tag.clear": "クリア",
"action.tag.empty": "タグがありません",
"action.tag.filter": "タグフィルター",
"action.tag.not-found": "検索に一致するタグがありません",
"action.tag.search": "タグ検索",
"action.view.github": "GitHub リポジトリを表示",
"error.feedback": "まだ問題が解決しませんかGitHub でフィードバックをお願いします。",
"error.reload": "再読み込み",

View File

@@ -8,6 +8,8 @@
"action.tag.clear": "지우기",
"action.tag.empty": "태그가 없습니다",
"action.tag.filter": "태그 필터",
"action.tag.not-found": "검색과 일치하는 태그가 없습니다",
"action.tag.search": "태그 검색",
"action.view.github": "GitHub 리포지토리 보기",
"error.feedback": "문제가 계속 발생하나요? GitHub 에 피드백을 남겨주세요. 감사합니다!",
"error.reload": "새로고침",

View File

@@ -8,6 +8,8 @@
"action.tag.clear": "清除",
"action.tag.empty": "暂无标签",
"action.tag.filter": "标签筛选",
"action.tag.not-found": "没有标签匹配您的搜索",
"action.tag.search": "搜索标签",
"action.view.github": "查看 GitHub 仓库",
"error.feedback": "仍然存在此问题?请在 Github 中提供反馈,谢谢!",
"error.reload": "重新加载",

View File

@@ -8,6 +8,8 @@
"action.tag.clear": "清除",
"action.tag.empty": "暫無標籤",
"action.tag.filter": "標籤篩選",
"action.tag.not-found": "沒有標籤符合您的搜尋",
"action.tag.search": "搜尋標籤",
"action.view.github": "查看 GitHub 倉庫",
"error.feedback": "仍然遇到此問題?請在 Github 中提供反饋,謝謝!",
"error.reload": "重新載入",

View File

@@ -8,6 +8,8 @@
"action.tag.clear": "清除",
"action.tag.empty": "暫無標籤",
"action.tag.filter": "標籤篩選",
"action.tag.not-found": "沒有標籤符合您的搜尋",
"action.tag.search": "搜尋標籤",
"action.view.github": "檢視 GitHub 存放庫",
"error.feedback": "仍然遇到此問題?請在 Github 中提供回饋,謝謝!",
"error.reload": "重新載入",