Skip to content

Commit cb38ed0

Browse files
CodFrmcyfung1031Copilot
authored
🐛 处理搜索交互逻辑 (#1023)
* 处理搜索交互逻辑 * 奇怪的问题 * 处理这个神奇的问题 * 加注释 * 加key去掉warn * onFilterDropdownVisibleChange + inputRef -> autoFocus * searchFilter * 中文 * SearchFilter.ts * lint * typescript * Update src/pages/options/routes/ScriptList/SearchFilter.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * 整理代码 --------- Co-authored-by: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3e7d803 commit cb38ed0

File tree

11 files changed

+154
-136
lines changed

11 files changed

+154
-136
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scriptcat",
3-
"version": "1.2.0-beta.5",
3+
"version": "1.2.0",
44
"description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!",
55
"author": "CodFrm",
66
"license": "GPLv3",

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "__MSG_scriptcat__",
4-
"version": "1.2.0.1600",
4+
"version": "1.2.0",
55
"author": "CodFrm",
66
"description": "__MSG_scriptcat_description__",
77
"options_ui": {

src/pages/components/ScriptResource/index.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,25 @@ import { ResourceClient } from "@App/app/service/service_worker/client";
44
import { message } from "@App/pages/store/global";
55
import { base64ToBlob } from "@App/pkg/utils/utils";
66
import { Button, Drawer, Input, Message, Popconfirm, Space, Table } from "@arco-design/web-react";
7-
import type { RefInputType } from "@arco-design/web-react/es/Input/interface";
87
import type { ColumnProps } from "@arco-design/web-react/es/Table";
98
import { IconDelete, IconDownload, IconSearch } from "@arco-design/web-react/icon";
10-
import React, { useEffect, useRef, useState } from "react";
9+
import React, { useEffect, useState } from "react";
1110
import { useTranslation } from "react-i18next";
1211

1312
type ResourceListItem = {
1413
key: string;
1514
} & Resource;
1615

16+
const resourceClient = new ResourceClient(message);
17+
1718
const ScriptResource: React.FC<{
1819
script?: Script;
1920
visible: boolean;
2021
onOk: () => void;
2122
onCancel: () => void;
2223
}> = ({ script, visible, onCancel, onOk }) => {
2324
const [data, setData] = useState<ResourceListItem[]>([]);
24-
const inputRef = useRef<RefInputType>(null);
2525
const { t } = useTranslation();
26-
const resourceClient = new ResourceClient(message);
2726

2827
useEffect(() => {
2928
if (!script) {
@@ -53,8 +52,8 @@ const ScriptResource: React.FC<{
5352
return (
5453
<div className="arco-table-custom-filter">
5554
<Input.Search
56-
ref={inputRef}
5755
searchButton
56+
autoFocus
5857
placeholder={t("enter_key")!}
5958
value={filterKeys[0] || ""}
6059
onChange={(value) => {
@@ -68,11 +67,6 @@ const ScriptResource: React.FC<{
6867
);
6968
},
7069
onFilter: (value, row) => !value || row.key.includes(value),
71-
onFilterDropdownVisibleChange: (v) => {
72-
if (v) {
73-
setTimeout(() => inputRef.current!.focus(), 1);
74-
}
75-
},
7670
},
7771
{
7872
title: t("type"),

src/pages/components/ScriptStorage/index.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import type { Script } from "@App/app/repo/scripts";
22
import { valueClient } from "@App/pages/store/features/script";
33
import { valueType } from "@App/pkg/utils/utils";
44
import { Button, Drawer, Form, Input, Message, Modal, Popconfirm, Select, Space, Table } from "@arco-design/web-react";
5-
import type { RefInputType } from "@arco-design/web-react/es/Input/interface";
65
import type { ColumnProps } from "@arco-design/web-react/es/Table";
76
import { IconDelete, IconEdit, IconSearch } from "@arco-design/web-react/icon";
8-
import React, { useEffect, useRef, useState } from "react";
7+
import React, { useEffect, useState } from "react";
98
import { useTranslation } from "react-i18next";
109

1110
const FormItem = Form.Item;
@@ -23,7 +22,6 @@ const ScriptStorage: React.FC<{
2322
}> = ({ script, visible, onCancel, onOk }) => {
2423
const [data, setData] = useState<ValueModel[]>([]);
2524
const [rawData, setRawData] = useState<{ [key: string]: any }>({});
26-
const inputRef = useRef<RefInputType>(null);
2725
const [currentValue, setCurrentValue] = useState<ValueModel>();
2826
const [visibleEdit, setVisibleEdit] = useState(false);
2927
const [isEdit, setIsEdit] = useState(false);
@@ -93,8 +91,8 @@ const ScriptStorage: React.FC<{
9391
return (
9492
<div className="arco-table-custom-filter">
9593
<Input.Search
96-
ref={inputRef}
9794
searchButton
95+
autoFocus
9896
placeholder={t("enter_key")!}
9997
value={filterKeys[0] || ""}
10098
onChange={(value) => {
@@ -108,11 +106,6 @@ const ScriptStorage: React.FC<{
108106
);
109107
},
110108
onFilter: (value, row) => !value || row.key.includes(value),
111-
onFilterDropdownVisibleChange: (v) => {
112-
if (v) {
113-
setTimeout(() => inputRef.current!.focus(), 1);
114-
}
115-
},
116109
},
117110
{
118111
title: t("value"),

src/pages/options/routes/ScriptList/ScriptTable.tsx

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
1+
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
22
import {
33
Avatar,
44
Button,
@@ -38,7 +38,6 @@ import {
3838
RiUploadCloudFill,
3939
} from "react-icons/ri";
4040
import { Link, useNavigate } from "react-router-dom";
41-
import type { RefInputType } from "@arco-design/web-react/es/Input/interface";
4241
import Text from "@arco-design/web-react/es/Typography/text";
4342
import type { DragEndEvent } from "@dnd-kit/core";
4443
import { closestCenter, DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
@@ -60,8 +59,7 @@ import { requestEnableScript, pinToTop, scriptClient, synchronizeClient } from "
6059
import { getCombinedMeta } from "@App/app/service/service_worker/utils";
6160
import { parseTags } from "@App/app/repo/metadata";
6261
import { EnableSwitch, HomeCell, MemoizedAvatar, ScriptSearchField, SourceCell, UpdateTimeCell } from "./components";
63-
import type { SetSearchRequest } from "./hooks";
64-
import type { SearchType } from "@App/app/service/service_worker/types";
62+
import { SearchFilter } from "./SearchFilter";
6563

6664
type ListType = ScriptLoading;
6765

@@ -420,8 +418,6 @@ interface ScriptTableProps {
420418
updateScripts: (uuids: string[], data: Partial<Script | ScriptLoading>) => void;
421419
setUserConfig: (config: { script: Script; userConfig: UserConfig; values: { [key: string]: any } }) => void;
422420
setCloudScript: (script: Script) => void;
423-
searchRequest: { keyword: string; type: SearchType };
424-
setSearchRequest: SetSearchRequest;
425421
handleDelete: (item: ScriptLoading) => void;
426422
handleConfig: (
427423
item: ScriptLoading,
@@ -440,8 +436,6 @@ export const ScriptTable = ({
440436
updateScripts,
441437
setUserConfig,
442438
setCloudScript,
443-
searchRequest,
444-
setSearchRequest,
445439
handleDelete,
446440
handleConfig,
447441
handleRunStop,
@@ -451,7 +445,6 @@ export const ScriptTable = ({
451445
const [action, setAction] = useState("");
452446
const [select, setSelect] = useState<Script[]>([]);
453447
const [selectColumn, setSelectColumn] = useState(0);
454-
const inputRef = useRef<RefInputType>(null);
455448
const navigate = useNavigate();
456449
const [savedWidths, setSavedWidths] = useState<{ [key: string]: number } | null>(null);
457450

@@ -494,25 +487,30 @@ export const ScriptTable = ({
494487
dataIndex: "name",
495488
sorter: (a, b) => a.name.localeCompare(b.name),
496489
filterIcon: <IconSearch />,
497-
filterDropdown: ({ confirm }: any) => {
490+
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
498491
return (
499492
<div className="arco-table-custom-filter flex flex-row gap-2">
500493
<ScriptSearchField
501494
t={t}
502-
defaultValue={searchRequest}
495+
autoFocus
496+
defaultValue={filterKeys?.[0] || { type: "auto", keyword: "" }}
497+
onChange={(req) => {
498+
setFilterKeys([{ type: req.type, keyword: req.keyword }]);
499+
SearchFilter.requestFilterResult(req);
500+
}}
503501
onSearch={(req) => {
504-
setSearchRequest(req);
502+
if (req.bySelect) return;
505503
confirm();
506504
}}
507-
inputRef={inputRef}
508505
/>
509506
</div>
510507
);
511508
},
512-
onFilterDropdownVisibleChange: (visible) => {
513-
if (visible) {
514-
setTimeout(() => inputRef.current!.focus(), 1);
509+
onFilter: (value, row) => {
510+
if (!value || !value.keyword) {
511+
return true;
515512
}
513+
return SearchFilter.checkByUUID(row.uuid);
516514
},
517515
className: "max-w-[240px] min-w-[100px]",
518516
render: (col: string, item: ListType) => <NameCell col={col} item={item} />,
@@ -620,8 +618,6 @@ export const ScriptTable = ({
620618
t,
621619
sidebarOpen,
622620
updateScripts,
623-
searchRequest,
624-
setSearchRequest,
625621
navigate,
626622
setSidebarOpen,
627623
setViewMode,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { SearchType } from "@App/app/service/service_worker/types";
2+
import { requestFilterResult } from "@App/pages/store/features/script";
3+
4+
export type SearchFilterKeyEntry = { type: SearchType; keyword: string };
5+
export type SearchFilterRequest = { type: SearchType; keyword: string; bySelect?: boolean }; // 两个Type日后可能会不同。先分开写。
6+
7+
// 静态变量不随重绘重置
8+
let lastReqType: SearchType | undefined = undefined;
9+
let lastKeyword: string = "";
10+
type SearchFilterCacheEntry = { code: boolean; name: boolean; auto: boolean };
11+
const searchFilterCache: Map<string, SearchFilterCacheEntry> = new Map();
12+
13+
export class SearchFilter {
14+
static async requestFilterResult(req: SearchFilterRequest) {
15+
if (req.keyword === lastKeyword) {
16+
lastReqType = req.type;
17+
return Promise.resolve(this);
18+
} else {
19+
const res = await requestFilterResult({ value: req.keyword });
20+
lastReqType = req.type;
21+
lastKeyword = req.keyword;
22+
searchFilterCache.clear();
23+
if (res && Array.isArray(res)) {
24+
for (const entry of res) {
25+
searchFilterCache.set(entry.uuid, {
26+
code: entry.code,
27+
name: entry.name,
28+
auto: entry.auto,
29+
});
30+
}
31+
}
32+
return this;
33+
}
34+
}
35+
36+
static checkByUUID(uuid: string): boolean {
37+
const result = searchFilterCache.get(uuid);
38+
if (!result) return false;
39+
switch (lastReqType) {
40+
case "auto":
41+
return result.auto;
42+
case "script_code":
43+
return result.code;
44+
case "name":
45+
return result.name;
46+
default:
47+
return false;
48+
}
49+
}
50+
}

src/pages/options/routes/ScriptList/components.tsx

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ListHomeRender } from "../utils";
1111
import { IconEdit, IconLink, IconUserAdd } from "@arco-design/web-react/icon";
1212
import type { SearchType } from "@App/app/service/service_worker/types";
1313
import type { TFunction } from "i18next";
14-
import type { RefInputType } from "@arco-design/web-react/es/Input";
14+
import type { SearchFilterKeyEntry, SearchFilterRequest } from "./SearchFilter";
1515

1616
export const EnableSwitch = React.memo(
1717
({
@@ -184,50 +184,52 @@ UpdateTimeCell.displayName = "UpdateTimeCell";
184184

185185
interface ScriptSearchFieldProps {
186186
t: TFunction<"translation", undefined>;
187-
defaultValue: { keyword: string; type: SearchType };
188-
onSearch?: (req: { keyword: string; type: SearchType }) => void;
189-
inputRef?: React.RefObject<RefInputType>;
187+
defaultValue?: SearchFilterKeyEntry;
188+
onChange?: (req: SearchFilterRequest) => void;
189+
onSearch?: (req: SearchFilterRequest) => void;
190+
autoFocus?: boolean;
190191
}
191192

192-
export const ScriptSearchField = React.memo(
193-
({ t, defaultValue, onSearch, inputRef }: ScriptSearchFieldProps) => {
194-
const [keyword, setKeyword] = React.useState(defaultValue?.keyword || "");
195-
const [type, setType] = React.useState<SearchType>(defaultValue?.type || "auto");
196-
return (
197-
<Space direction="horizontal">
198-
<Select
199-
className="flex-1"
200-
triggerProps={{ autoAlignPopupWidth: false, autoAlignPopupMinWidth: true, position: "bl" }}
201-
size="small"
202-
value={type}
203-
onChange={(value) => {
204-
setType(value as SearchType);
205-
onSearch?.({ keyword, type: value as SearchType });
206-
}}
207-
>
208-
<Select.Option value="auto">{t("auto")}</Select.Option>
209-
<Select.Option value="name">{t("name")}</Select.Option>
210-
<Select.Option value="script_code">{t("script_code")}</Select.Option>
211-
</Select>
212-
<Input.Search
213-
ref={inputRef}
214-
size="small"
215-
searchButton
216-
style={{ width: 280 }}
217-
value={keyword}
218-
placeholder={t("enter_search_value", { search: `${t("name")}/${t("script_code")}` })!}
219-
onChange={(value) => {
220-
setKeyword(value);
221-
}}
222-
onSearch={(value) => {
223-
onSearch?.({ keyword: value, type });
224-
}}
225-
/>
226-
</Space>
227-
);
228-
},
229-
(prevProps, nextProps) => {
230-
return prevProps.t === nextProps.t && prevProps.defaultValue === nextProps.defaultValue;
231-
}
232-
);
233-
ScriptSearchField.displayName = "ScriptSearchField";
193+
export const ScriptSearchField = ({ t, defaultValue, onChange, onSearch, autoFocus }: ScriptSearchFieldProps) => {
194+
const [keyword, setKeyword] = React.useState(defaultValue?.keyword || "");
195+
const [type, setType] = React.useState<SearchType>(defaultValue?.type || "auto");
196+
return (
197+
<Space direction="horizontal">
198+
<Select
199+
className="flex-1"
200+
triggerProps={{ autoAlignPopupWidth: false, autoAlignPopupMinWidth: true, position: "bl" }}
201+
size="small"
202+
value={type}
203+
onChange={(value) => {
204+
setType(value as SearchType);
205+
onChange?.({ keyword, type: value as SearchType });
206+
onSearch?.({ keyword, type: value as SearchType, bySelect: true });
207+
}}
208+
>
209+
<Select.Option value="auto">{t("auto")}</Select.Option>
210+
<Select.Option value="name">{t("name")}</Select.Option>
211+
<Select.Option value="script_code">{t("script_code")}</Select.Option>
212+
</Select>
213+
<Input.Search
214+
size="small"
215+
searchButton
216+
autoFocus={autoFocus}
217+
style={{ width: 280 }}
218+
value={keyword}
219+
placeholder={
220+
t("enter_search_value", {
221+
search:
222+
type === "auto" ? `${t("name")}/${t("script_code")}` : type === "name" ? t("name") : t("script_code"),
223+
})!
224+
}
225+
onChange={(value) => {
226+
onChange?.({ keyword: value, type });
227+
setKeyword(value);
228+
}}
229+
onSearch={(value) => {
230+
onSearch?.({ keyword: value, type, bySelect: false });
231+
}}
232+
/>
233+
</Space>
234+
);
235+
};

0 commit comments

Comments
 (0)