feat: show search page for multiple search results

This commit is contained in:
mrbunker
2024-09-21 16:28:55 +08:00
parent 476dd008cd
commit 7c0ea49fb1
4 changed files with 126 additions and 80 deletions

View File

@@ -6,37 +6,45 @@ import { SiteItem, siteList } from "@/utils/siteList";
import type { LibItem } from "@/utils/libSites";
const App = memo(function ({ libItem, CODE }: { libItem: LibItem; CODE: string }) {
// 默认不显示
// 默认不显示
const DEF_DIS = [
...["AvJoy", "baihuse", "GGJAV", "AV01", "18sex", "highporn"],
...["JavBus", "JavDB", "JAVLib", "MISSAV_"],
];
const [disables, setDisables] = useState(GM_getValue<SiteItem["name"][]>("disable", DEF_DIS));
const [multipleNavi, setMultipleNavi] = useState(GM_getValue<boolean>("multipleNavi", true));
return (
<>
<div class="jop-list">
{siteList.map((siteItem) => {
const hidden = disables.find((disItem) => disItem === siteItem.name) === undefined;
const sameSite = libItem.name !== siteItem.disableLibItemName;
if (hidden && sameSite) {
return <SiteBtn siteItem={siteItem} CODE={CODE} key={siteItem.name} />;
} else {
return <></>;
}
})}
{siteList
.filter(
(siteItem) =>
!disables.includes(siteItem.name) && libItem.name !== siteItem.disableLibItemName,
)
.map((siteItem) => (
<SiteBtn
siteItem={siteItem}
CODE={CODE}
key={siteItem.name}
multipleNavi={multipleNavi}
/>
))}
</div>
<div>
<Setting
siteList={siteList}
setDisables={(disable) => {
setDisables(disable);
GM_setValue("disable", disable);
}}
disables={disables}
/>
</div>
<Setting
siteList={siteList}
setDisables={(disable) => {
setDisables(disable);
GM_setValue("disable", disable);
}}
multipleNavi={multipleNavi}
setMultipleNavi={(multipleNavi) => {
setMultipleNavi(multipleNavi);
GM_setValue("multipleNavi", multipleNavi);
}}
disables={disables}
/>
</>
);
});

View File

@@ -1,16 +1,17 @@
import { Dispatch, StateUpdater, useState } from "preact/hooks";
import { GM_setValue } from "$";
import { SiteItem } from "@/utils/siteList";
import Checkbox from "./Checkbox";
const Setting = ({
siteList,
setDisables,
disables,
}: {
type Props = {
siteList: SiteItem[];
setDisables: Dispatch<StateUpdater<string[]>>;
disables: SiteItem["name"][];
}) => {
multipleNavi: boolean;
setMultipleNavi: Dispatch<StateUpdater<boolean>>;
};
const Setting = ({ siteList, setDisables, disables, multipleNavi, setMultipleNavi }: Props) => {
const [showSetting, setShowSetting] = useState(true);
const hanleListChange = (item: SiteItem, isHidden: boolean) => {
@@ -21,6 +22,11 @@ const Setting = ({
}
};
const handleNaviChange = (checked: boolean) => {
setMultipleNavi(checked);
GM_setValue("multipleNavi", checked);
};
return (
<>
{!showSetting && (
@@ -31,9 +37,10 @@ const Setting = ({
{showSetting && (
<>
<div className="jop-setting">
<Group title="勾选默认显示的网站:">
<Group title="勾选默认展示">
{siteList.map((item) => {
const isHidden = disables.includes(item.name);
return (
<Checkbox
label={item.name}
@@ -43,6 +50,16 @@ const Setting = ({
);
})}
</Group>
<Group title="其他设置">
<Checkbox
label="展示多个搜索结果"
value={multipleNavi}
tip="一个站点内出现多条匹配结果时,打开后跳转搜索结果页"
onChange={handleNaviChange}
/>
{/* <Checkbox label="简洁模式" value={true} onChange={() => {}} /> */}
</Group>
</div>
<div

View File

@@ -1,14 +1,19 @@
import { handleFetch, handleFetchJavBle } from "@/utils/xhr";
import { fetcher } from "@/utils/xhr";
import { SiteItem } from "@/utils/siteList";
import { memo, useEffect, useState } from "preact/compat";
interface Status {
isSuccess: "pedding" | "rejected" | "fulfilled";
hasSubtitle: boolean;
hasLeakage: boolean;
targetLink: string;
resultLink?: string;
tag?: string;
}
const SiteBtn = memo(({ siteItem, CODE }: { siteItem: SiteItem; CODE: string }) => {
type Props = {
siteItem: SiteItem;
CODE: string;
multipleNavi?: boolean;
};
const SiteBtn = memo(({ siteItem, CODE, multipleNavi }: Props) => {
const { name, codeFormater } = siteItem;
/** 格式化 CODE */
const formatCode = codeFormater ? codeFormater(CODE) : CODE;
@@ -16,23 +21,22 @@ const SiteBtn = memo(({ siteItem, CODE }: { siteItem: SiteItem; CODE: string })
const [status, setStatus] = useState<Status>({
isSuccess: "pedding",
hasSubtitle: false,
hasLeakage: false,
targetLink: "",
tag: "",
resultLink: "",
});
const { isSuccess, hasSubtitle, hasLeakage, targetLink } = status;
const { isSuccess, tag, resultLink } = status;
useEffect(() => {
const fetchMethod = name === "Jable" ? handleFetchJavBle : handleFetch;
fetchMethod(siteItem, link, formatCode).then((res) => {
fetcher({ siteItem, targetLink: link, CODE: formatCode }).then((res) => {
const resultLink = multipleNavi && res.multipleRes ? res.multipResLink : res.targetLink;
setStatus({
isSuccess: res.isSuccess ? "fulfilled" : "rejected",
hasSubtitle: res.hasSubtitle,
hasLeakage: res.hasLeakage,
targetLink: res.targetLink,
tag: multipleNavi && res.multipleRes ? "多结果" : res.tag,
resultLink,
});
});
}, [handleFetch, siteItem, CODE, link]);
}, [fetcher, siteItem, CODE, link, multipleNavi]);
const colorClass =
isSuccess === "pedding"
@@ -45,14 +49,9 @@ const SiteBtn = memo(({ siteItem, CODE }: { siteItem: SiteItem; CODE: string })
<a
className={"jop-button " + colorClass}
target="_blank"
href={targetLink === "" ? link : targetLink}
href={resultLink === "" ? link : resultLink}
>
{(hasSubtitle || hasLeakage) && (
<div className="jop-button_label">
{hasSubtitle && <span> </span>}
{hasLeakage && <span> </span>}
</div>
)}
{tag && <div className="jop-button_label">{tag}</div>}
{/* 加载动画 */}
{/* {isSuccess === "pedding" && <span className="jop-loading"> </span>} */}

View File

@@ -3,9 +3,10 @@ import type { DomQuery_get, DomQuery_parser, SiteItem } from "./siteList";
export type FetchResult = {
isSuccess: boolean;
targetLink: string;
hasSubtitle: boolean;
hasLeakage: boolean;
targetLink?: string;
tag?: string;
multipResLink?: string;
multipleRes?: boolean;
};
/** 针对视频播放页进行解析,寻找字幕等信息 */
@@ -38,35 +39,47 @@ function serachPageParser(
{ linkQuery, titleQuery, listIndex = 0 }: DomQuery_parser,
siteHostName: string,
CODE: string,
searchPageLink: string,
) {
const doc = new DOMParser().parseFromString(responseText, "text/html");
const linkNode = linkQuery ? doc.querySelectorAll<HTMLAnchorElement>(linkQuery)[listIndex] : null;
const titleNode = titleQuery ? doc.querySelectorAll(titleQuery)[listIndex] : null;
const titleNodes = titleQuery ? doc.querySelectorAll(titleQuery) : [];
const linkNodes = linkQuery ? doc.querySelectorAll<HTMLAnchorElement>(linkQuery) : [];
const titleNode = titleNodes[listIndex];
const linkNode = linkNodes[listIndex];
const titleNodeText = titleNode ? titleNode?.outerHTML : "";
const codeRegex = /[a-zA-Z]{3,5}-\d{3,5}/;
const matchCode = titleNodeText.match(codeRegex);
const isSuccess =
linkNode && titleNode && matchCode && isCaseInsensitiveEqual(matchCode[0], CODE);
if (isSuccess) {
const targetLinkText = linkNode.href.replace(linkNode.hostname, siteHostName);
return {
isSuccess: true,
targetLink: targetLinkText,
hasLeakage: regEnum.leakage.test(titleNodeText),
hasSubtitle: regEnum.subtitle.test(titleNodeText),
};
} else {
return { targetLink: "", isSuccess: false, hasSubtitle: false, hasLeakage: false };
if (!isSuccess) {
return { isSuccess: false };
}
const targetLinkText = linkNode.href.replace(linkNode.hostname, siteHostName);
const hasLeakage = regEnum.leakage.test(titleNodeText);
const hasSubtitle = regEnum.subtitle.test(titleNodeText);
const tags = [];
if (hasLeakage) tags.push("无码");
if (hasSubtitle) tags.push("字幕");
return {
isSuccess: true,
targetLink: targetLinkText,
multipResLink: searchPageLink,
multipleRes: titleNodes.length > 1,
tag: tags.join(" "),
};
}
export const handleFetch = async (
siteItem: SiteItem,
targetLink: string,
CODE: string,
): Promise<FetchResult> => {
type Args = {
siteItem: SiteItem;
targetLink: string;
CODE: string;
};
export const baseFetcher = async ({ siteItem, targetLink, CODE }: Args): Promise<FetchResult> => {
try {
const response = await gmGet({ url: targetLink });
if (isErrorCode(response.status)) {
@@ -82,26 +95,35 @@ export const handleFetch = async (
};
} else {
return {
...serachPageParser(response.responseText, siteItem.domQuery, siteItem.hostname, CODE),
...serachPageParser(
response.responseText,
siteItem.domQuery,
siteItem.hostname,
CODE,
targetLink,
),
};
}
} catch (error) {
return {
isSuccess: false,
targetLink: targetLink,
hasSubtitle: false,
hasLeakage: false,
};
}
};
/** jable 有些域名是带 -c */
export const handleFetchJavBle = async (
siteItem: SiteItem,
targetLink: string,
CODE: string,
): Promise<FetchResult> => {
const res = await handleFetch(siteItem, targetLink, CODE);
const newLink = targetLink.slice(0, -1) + "-c/";
return res.isSuccess ? res : await handleFetch(siteItem, newLink, CODE);
export const javbleFetcher = async (args: Args): Promise<FetchResult> => {
const res = await baseFetcher(args);
if (res.isSuccess) return res;
const newLink = args.targetLink.slice(0, -1) + "-c/";
return await baseFetcher({ ...args, targetLink: newLink });
};
export const fetcher = (args: Args) => {
if (args.siteItem.name === "Jable") {
return javbleFetcher(args);
}
return baseFetcher(args);
};