Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/locales/de-DE/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "Favicon-Dienst",
"favicon_service_desc": "Dienst zum Abrufen von Website-Symbolen auswählen",
"favicon_service_none": "Deaktiviert",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "Favicon Service",
"favicon_service_desc": "Choose the service for fetching website icons",
"favicon_service_none": "Disabled",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ja-JP/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "Favicon サービス",
"favicon_service_desc": "ウェブサイトアイコンの取得サービスを選択",
"favicon_service_none": "無効",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ko-KR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "파비콘 서비스",
"favicon_service_desc": "웹사이트 아이콘을 가져올 서비스를 선택하세요",
"favicon_service_none": "비활성화",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/pt-BR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "Serviço de favicon",
"favicon_service_desc": "Escolha o serviço para buscar os ícones dos sites",
"favicon_service_none": "Desativado",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ru-RU/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "Сервис Favicon",
"favicon_service_desc": "Выберите сервис для получения значков сайтов",
"favicon_service_none": "Выключено",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/tr-TR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "Favicon Hizmeti",
"favicon_service_desc": "Web sitesi simgelerini almak için hizmeti seçin",
"favicon_service_none": "Devre Dışı",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/vi-VN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "Dịch vụ Favicon",
"favicon_service_desc": "Chọn dịch vụ để lấy biểu tượng trang web",
"favicon_service_none": "Đã tắt",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh-CN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "图标服务",
"favicon_service_desc": "选择获取网站图标的服务",
"favicon_service_none": "禁用",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh-TW/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
},
"favicon_service": "圖示服務",
"favicon_service_desc": "選擇取得網站圖示的服務",
"favicon_service_none": "停用",
"favicon_service_scriptcat": "ScriptCat",
"favicon_service_google": "Google",
"favicon_service_duckduckgo": "DuckDuckGo",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ describe("界面分区-图标服务", () => {
await screen.findByText("扩展图标徽标");
expect(screen.getAllByText("扩展图标徽标")).toHaveLength(1);
});

// 图标服务可整体关闭:选中 none 时下拉框需回显「禁用」,否则用户无从选择这一档
it("图标服务为 none 时下拉框回显禁用", async () => {
get.mockImplementation((key: string) => Promise.resolve(key === "favicon_service" ? "none" : ""));
render(<InterfaceSection register={() => () => {}} />);
expect(await screen.findByText("禁用")).toBeInTheDocument();
});
});

describe("界面分区-popup 布局", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function InterfaceSection({ register }: { register: (id: string) => (el:
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">{t("settings:favicon_service_none")}</SelectItem>
<SelectItem value="scriptcat">{t("settings:favicon_service_scriptcat")}</SelectItem>
<SelectItem value="google">{t("settings:favicon_service_google")}</SelectItem>
<SelectItem value="duckduckgo">{t("settings:favicon_service_duckduckgo")}</SelectItem>
Expand Down
51 changes: 51 additions & 0 deletions src/pages/options/routes/SubscribeList/components.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
import { render, screen, cleanup, waitFor } from "@testing-library/react";

const { get } = vi.hoisted(() => ({ get: vi.fn() }));
vi.mock("@App/pages/store/global", async () => {
const { createGlobalStoreMock } = await import("@Tests/mocks/pageStores.ts");
return createGlobalStoreMock({ systemConfig: { get } });
});

vi.mock("@App/pages/store/features/subscribe", () => ({
requestCheckSubscribeUpdate: vi.fn(() => Promise.resolve(false)),
}));

import { initTestLanguage } from "@Tests/initTestLanguage";
import { TooltipProvider } from "@App/pages/components/ui/tooltip";
import { PermissionFavicons } from "./components";

beforeAll(() => {
initTestLanguage("zh-CN");
});

afterEach(() => {
cleanup();
get.mockReset();
});

const renderFavicons = () =>
render(
<TooltipProvider>
<PermissionFavicons connect={["example.com"]} />
</TooltipProvider>
);

describe("订阅 @connect 域名图标", () => {
it("图标服务可用时以站点 favicon 呈现", async () => {
get.mockResolvedValue("scriptcat");
renderFavicons();

const img = await screen.findByAltText("example.com");
expect(img).toHaveAttribute("src", "https://example.com/favicon.ico");
});

it("图标服务禁用时不向站点请求 favicon", async () => {
get.mockResolvedValue("none");
const { container } = renderFavicons();

// 配置读取是异步的:等到读取完成后仍不能出现 <img>,否则请求早已发出
await waitFor(() => expect(get).toHaveBeenCalledWith("favicon_service"));
expect(container.querySelector("img")).toBeNull();
});
});
10 changes: 7 additions & 3 deletions src/pages/options/routes/SubscribeList/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useTranslation } from "react-i18next";
import { cn } from "@App/pkg/utils/cn";
import { notify } from "@App/pages/components/ui/toast";
import { NameAvatar } from "@App/pages/components/NameAvatar";
import { useSystemConfig } from "@App/pages/options/hooks/useSystemConfig";
import { Globe, RefreshCw, Rss, Trash2, CircleArrowUp, Check, Loader2, Link } from "lucide-react";

// ========== SubscribeIcon ==========
Expand Down Expand Up @@ -45,12 +46,12 @@ SubscribeEnableSwitch.displayName = "SubscribeEnableSwitch";

// ========== PermissionFavicons ==========
// @connect 域名以站点 favicon 呈现,加载失败回退到地球图标
function DomainFavicon({ domain }: { domain: string }) {
function DomainFavicon({ domain, showIcon }: { domain: string; showIcon: boolean }) {
const [imgError, setImgError] = useState(false);
return (
<Tooltip>
<TooltipTrigger asChild>
{imgError ? (
{!showIcon || imgError ? (
<Globe className="w-3.5 h-3.5 text-muted-foreground/50" />
) : (
<img
Expand All @@ -67,13 +68,16 @@ function DomainFavicon({ domain }: { domain: string }) {
}

export const PermissionFavicons = React.memo(({ connect, maxShow = 5 }: { connect?: string[]; maxShow?: number }) => {
const [faviconService] = useSystemConfig("favicon_service");
// 配置读取完成前一律先显示占位:<img> 一旦挂载请求就已发出,关闭图标获取后再撤下已经晚了
const showIcon = !!faviconService && faviconService !== "none";
if (!connect || connect.length === 0) return <span className="text-xs text-muted-foreground/50">{"-"}</span>;
const visible = connect.slice(0, maxShow);
const extra = connect.length - maxShow;
return (
<div className="flex items-center gap-1.5">
{visible.map((domain) => (
<DomainFavicon key={domain} domain={domain} />
<DomainFavicon key={domain} domain={domain} showIcon={showIcon} />
))}
{extra > 0 && <span className="text-[10px] text-muted-foreground ml-0.5">{`+${extra}`}</span>}
</div>
Expand Down
31 changes: 31 additions & 0 deletions src/pages/store/favicons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
parseFaviconsNew,
fetchIconByService,
fetchIconByDomain,
loadScriptFavicons,
} from "@App/pages/store/favicons";
import type { Script } from "@App/app/repo/scripts";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

describe("extractFaviconsDomain", () => {
Expand Down Expand Up @@ -172,9 +174,38 @@ describe("fetchIconByService", () => {
]);
});

it("none 服务不应返回任何图标地址", async () => {
const result = await fetchIconByService("example.com", "none");
expect(result).toEqual([]);
expect(fetch).not.toHaveBeenCalled();
});

// local 服务的具体行为已在 fetchIconByDomain 测试中充分覆盖
});

describe("loadScriptFavicons", () => {
afterEach(() => {
vi.restoreAllMocks();
});

it("图标服务设为 none 时不产出图标,也不发起任何请求", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const scripts = [
{ uuid: "uuid-1", metadata: { match: ["https://example.com/*"] } },
{ uuid: "uuid-2", metadata: { match: ["https://another.com/*"] } },
] as unknown as Script[];

const chunks = [];
for await (const chunk of loadScriptFavicons(scripts, "none")) {
chunks.push(chunk);
}

expect(chunks).toEqual([]);
expect(fetchMock).not.toHaveBeenCalled();
});
});

describe("fetchIconByDomain", () => {
afterEach(() => {
vi.restoreAllMocks();
Expand Down
6 changes: 6 additions & 0 deletions src/pages/store/favicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ export async function fetchIconByDomain(domain: string): Promise<string[]> {
*/
export async function fetchIconByService(domain: string, service: FaviconService): Promise<string[]> {
switch (service) {
case "none":
// 用户关闭了图标获取,不产生任何外部请求
return [];

case "scriptcat":
/**
* ScriptCat 图标服务
Expand Down Expand Up @@ -382,6 +386,8 @@ type TFaviconStack = { chunkResults: FavIconResult[]; pendingCount: number };

// 处理favicon加载,以批次方式处理
export const loadScriptFavicons = async function* (scripts: Script[], service: FaviconService = "scriptcat") {
// 关闭图标获取时直接结束:不读写 favicon 缓存,也不发起任何请求
if (service === "none") return;
const stack: TFaviconStack[] = [];
const asyncWaiter: { promise?: any; resolve?: any } = {};
const createPromise = () => {
Expand Down
3 changes: 2 additions & 1 deletion src/pkg/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const DEFAULT_CLOUD_SYNC_STATE: CloudSyncState = {
counts: { total: 0, overwrite: 0, conflict: 0, failed: 0 },
};

export type FaviconService = "scriptcat" | "google" | "duckduckgo" | "icon-horse" | "local";
// "none" 表示彻底关闭网站图标获取:不向任何图标服务或目标站点发起请求
export type FaviconService = "none" | "scriptcat" | "google" | "duckduckgo" | "icon-horse" | "local";

export type CATFileStorage = {
filesystem: FileSystemType;
Expand Down
Loading