feat(web): 设置页按新分类重排,加搜索、缓存分区与旧锚点跳转
- 左栏三组分区 + 当前分区的节;窄屏为搜索框、分组下拉和节的横排胶囊 - 搜索框按分区、节、节别名和字段名定位,选中字段时滚到该字段并聚焦 - 旧 hash(#automation/cache、#api-keys、#library/scrape 等)replace 到新位置 - 下载器分区放下载盘水位与保种缺省值,名字与实例抽屉统一(暂停线 / 恢复线 / 保种天数) - 新增获取、整理与刮削、系统、缓存分区;API Key 并入账户与安全,快捷键移到界面 - 补上缺的入口:最小视频体积、子会话并发与层数、搜索站点;日志保留天数(已废弃)不再显示 - 缓存分区按区列出条数、占用、最近写入,单区清空二次确认;TMDB 与图片缓存并入 - 保存提示统一为「已保存「节名」」;按入库口径把相关文案改成「整理」 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
93a6c8a910
commit
8071ad33f9
23 files changed
+1499
-642
No files matched your search
@@ -0,0 +1,116 @@
|
||||
import { Search, X } from "lucide-react";
|
||||
import { useId, useMemo, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Input } from "~/components/ui/input.tsx";
|
||||
import { cn } from "~/lib/utils.ts";
|
||||
import { type SettingsSearchHit, searchSettings } from "./search-index.ts";
|
||||
import { settingsHash } from "./sections.tsx";
|
||||
|
||||
/** 设置页搜索框:按设置名、节名找位置;选中后跳到该节,字段会被聚焦 */
|
||||
export function SettingsSearch({ className }: { className?: string }) {
|
||||
const navigate = useNavigate();
|
||||
const listId = useId();
|
||||
const [q, setQ] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
const [active, setActive] = useState(0);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const hits = useMemo(() => searchSettings(q), [q]);
|
||||
const show = open && q.trim() !== "";
|
||||
|
||||
const pick = (h: SettingsSearchHit) => {
|
||||
setOpen(false);
|
||||
setQ("");
|
||||
navigate({ hash: settingsHash(h.section, h.anchor) }, { state: h.field ? { field: h.field } : null });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("relative", className)}>
|
||||
<Search
|
||||
className="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
|
||||
aria-hidden
|
||||
/>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
enterKeyHint="search"
|
||||
role="combobox"
|
||||
aria-expanded={show}
|
||||
aria-controls={listId}
|
||||
aria-autocomplete="list"
|
||||
aria-label="搜索设置"
|
||||
placeholder="搜索设置"
|
||||
value={q}
|
||||
className="pr-8 pl-8"
|
||||
onChange={(e) => {
|
||||
setQ(e.target.value);
|
||||
setActive(0);
|
||||
setOpen(true);
|
||||
}}
|
||||
onFocus={() => setOpen(true)}
|
||||
onBlur={() => window.setTimeout(() => setOpen(false), 120)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setActive((i) => Math.min(i + 1, hits.length - 1));
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setActive((i) => Math.max(i - 1, 0));
|
||||
} else if (e.key === "Enter") {
|
||||
const h = hits[active];
|
||||
if (h) {
|
||||
e.preventDefault();
|
||||
pick(h);
|
||||
}
|
||||
} else if (e.key === "Escape") {
|
||||
setQ("");
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{q ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="清除搜索"
|
||||
className="absolute top-1/2 right-2 -translate-y-1/2 rounded-full p-0.5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => {
|
||||
setQ("");
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
{show ? (
|
||||
<div
|
||||
id={listId}
|
||||
role="listbox"
|
||||
aria-label="搜索结果"
|
||||
className="pg-float absolute inset-x-0 top-full z-30 mt-1.5 max-h-80 animate-pg-pop overflow-y-auto rounded-xl p-1"
|
||||
>
|
||||
{hits.length === 0 ? (
|
||||
<p className="px-3 py-2 text-caption text-muted-foreground">没有匹配的设置</p>
|
||||
) : (
|
||||
hits.map((h, i) => (
|
||||
<button
|
||||
key={h.key}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={i === active}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onMouseEnter={() => setActive(i)}
|
||||
onClick={() => pick(h)}
|
||||
className={cn(
|
||||
"block w-full rounded-lg px-3 py-1.5 text-left transition-colors duration-micro",
|
||||
i === active ? "bg-foreground/[0.06]" : "",
|
||||
)}
|
||||
>
|
||||
<span className="block truncate text-sm text-foreground">{h.label}</span>
|
||||
<span className="block truncate text-meta text-muted-foreground">{h.where}</span>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user