mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
feat: support tags search (#68)
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -52,4 +52,4 @@
|
||||
"exportall.config.folderListener": [
|
||||
"/apps/ssr/src/schemas"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 表示自动计算
|
||||
})
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "再読み込み",
|
||||
|
||||
@@ -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": "새로고침",
|
||||
|
||||
@@ -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": "重新加载",
|
||||
|
||||
@@ -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": "重新載入",
|
||||
|
||||
@@ -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": "重新載入",
|
||||
|
||||
Reference in New Issue
Block a user