diff --git a/packages/core/src/filtered-proxy-groups.ts b/packages/core/src/filtered-proxy-groups.ts index 3fddf39..1c45a9d 100644 --- a/packages/core/src/filtered-proxy-groups.ts +++ b/packages/core/src/filtered-proxy-groups.ts @@ -32,10 +32,6 @@ function getNodeSourceIds(node: ParsedNode): string[] { .map((s) => s.trim()); } -function escapeRegExp(input: string): string { - return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - function compileRegex(pattern?: string): RegExp | null { const raw = typeof pattern === "string" ? pattern.trim() : ""; if (!raw) return null; diff --git a/packages/core/src/generator/index.ts b/packages/core/src/generator/index.ts index c38f16d..418a0b1 100644 --- a/packages/core/src/generator/index.ts +++ b/packages/core/src/generator/index.ts @@ -25,7 +25,6 @@ import type { ClashConfig, UserConfig, TemplateType, ProxyGroup } from "@subboos import type { CustomProxyGroup } from "@subboost/core/types/config"; import type { DialerProxyGroup, ModuleRuleOverride } from "@subboost/core/types/template-config"; import type { FilteredProxyGroup } from "@subboost/core/types/filtered-proxy-group"; -import { TEMPLATES } from "../templates"; import { collectDnsPolicyEntries, configToYaml } from "./yaml"; import type { ModuleRuleExclusions } from "./module-rules"; import { isMihomoSupportedProxyNode, normalizeMihomoVlessForGeneration } from "@subboost/core/mihomo/proxy-sanitizer"; @@ -182,9 +181,6 @@ export function generateClashConfig(options: GenerateOptions): ClashConfig { proxyGroupNameOverrides, } = options; - // 获取模板配置 - const templateConfig = TEMPLATES[template]; - // 合并用户配置 const config: UserConfig = { ...buildDefaultUserConfig(template), diff --git a/packages/ui/src/product/converter/advanced-mode/root.tsx b/packages/ui/src/product/converter/advanced-mode/root.tsx index 20a5b13..471f14d 100644 --- a/packages/ui/src/product/converter/advanced-mode/root.tsx +++ b/packages/ui/src/product/converter/advanced-mode/root.tsx @@ -1,7 +1,6 @@ "use client"; import * as React from "react"; -import { useConfigStore } from "@subboost/ui/store/config-store"; import { DialerProxyGroupsSection } from "./sections/dialer-proxy-groups-section"; import { DnsSection } from "./sections/dns-section"; import { FilteredProxyGroupsSection } from "./sections/filtered-proxy-groups-section"; diff --git a/packages/ui/src/product/converter/advanced-mode/sections/node-management/bulk-edit-dialog.test.ts b/packages/ui/src/product/converter/advanced-mode/sections/node-management/bulk-edit-dialog.test.ts index 2003d1b..4409f44 100644 --- a/packages/ui/src/product/converter/advanced-mode/sections/node-management/bulk-edit-dialog.test.ts +++ b/packages/ui/src/product/converter/advanced-mode/sections/node-management/bulk-edit-dialog.test.ts @@ -197,19 +197,19 @@ describe("NodeManagementBulkEditDialog", () => { }); it("shows regex errors, skipped nodes, empty names, and no-match previews", () => { - let result = renderDialog({ 0: "[", 2: "(" }); - expect(result.html).toContain("无效正则"); + const invalidRegexResult = renderDialog({ 0: "[", 2: "(" }); + expect(invalidRegexResult.html).toContain("无效正则"); buttonByText("完成").onClick(); - expect(result.props.bulkRenameNodes).not.toHaveBeenCalled(); + expect(invalidRegexResult.props.bulkRenameNodes).not.toHaveBeenCalled(); - result = renderDialog({ 0: "Locked", 2: "Locked", 3: "Open" }); - expect(result.html).toContain("跳过:当前节点命名模板无法解析"); + const skippedNodesResult = renderDialog({ 0: "Locked", 2: "Locked", 3: "Open" }); + expect(skippedNodesResult.html).toContain("跳过:当前节点命名模板无法解析"); - result = renderDialog({ 0: "Beta", 2: ".*", 3: "" }); - expect(result.html).toContain("跳过:新名称为空"); + const emptyNameResult = renderDialog({ 0: "Beta", 2: ".*", 3: "" }); + expect(emptyNameResult.html).toContain("跳过:新名称为空"); - result = renderDialog({ 0: "Missing" }); - expect(result.html).toContain("暂无匹配节点"); + const noMatchResult = renderDialog({ 0: "Missing" }); + expect(noMatchResult.html).toContain("暂无匹配节点"); }); it("auto-fills listener ports and clears listener-port UI state", () => { @@ -227,70 +227,70 @@ describe("NodeManagementBulkEditDialog", () => { }); it("validates listener-port input and conflicts", () => { - let result = renderDialog({ 0: "Alpha", 6: "70000" }); + renderDialog({ 0: "Alpha", 6: "70000" }); buttonByText("自动填充监听端口").onClick(); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "起始监听端口需为 1-65535 的整数", variant: "warning" })); - result = renderDialog({ 0: "Alpha", 6: "" }); + renderDialog({ 0: "Alpha", 6: "" }); buttonByText("自动填充监听端口").onClick(); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "起始监听端口需为 1-65535 的整数", variant: "warning" })); - result = renderDialog({ 0: "Alpha", 6: "abc" }); + renderDialog({ 0: "Alpha", 6: "abc" }); buttonByText("自动填充监听端口").onClick(); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "起始监听端口需为 1-65535 的整数", variant: "warning" })); - result = renderDialog({ 0: "Alpha|Beta", 6: "65535" }); + renderDialog({ 0: "Alpha|Beta", 6: "65535" }); buttonByText("自动填充监听端口").onClick(); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "监听端口超出范围", variant: "warning" })); - result = renderDialog( + const conflictResult = renderDialog( { 0: "Alpha|Beta", 6: "42000" }, { listenerPorts: { Other: 42001 } }, ); buttonByText("自动填充监听端口").onClick(); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "监听端口冲突:42001", variant: "destructive" })); - expect(result.props.bulkSetListenerPorts).not.toHaveBeenCalled(); + expect(conflictResult.props.bulkSetListenerPorts).not.toHaveBeenCalled(); - result = renderDialog({ 0: "Missing", 6: "42000" }); + renderDialog({ 0: "Missing", 6: "42000" }); buttonByText("自动填充监听端口").onClick(); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "暂无匹配节点", variant: "warning" })); }); it("bulk-removes listener ports and hides listener controls when disabled", () => { - let result = renderDialog({ 0: "Alpha|Beta" }); + const enabledResult = renderDialog({ 0: "Alpha|Beta" }); buttonByText("批量删除监听端口").onClick(); - expect(result.props.bulkSetListenerPorts).toHaveBeenCalledWith({ + expect(enabledResult.props.bulkSetListenerPorts).toHaveBeenCalledWith({ "[HK] Alpha One": null, "Beta Two": null, }); - expect(result.props.onClearListenerPortUiState).toHaveBeenCalledWith(["[HK] Alpha One", "Beta Two"]); + expect(enabledResult.props.onClearListenerPortUiState).toHaveBeenCalledWith(["[HK] Alpha One", "Beta Two"]); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "已批量删除监听端口 2 个节点", variant: "success" })); - result = renderDialog({}, { listenerPortEnabled: false }); - expect(result.html).not.toContain("监听端口批量操作"); + const disabledResult = renderDialog({}, { listenerPortEnabled: false }); + expect(disabledResult.html).not.toContain("监听端口批量操作"); }); it("handles no-op previews, empty node names, disabled actions, and listener-port reservations", () => { - let result = renderDialog( + const noOpResult = renderDialog( { 0: "Alpha", 4: false, 5: false, 6: "43000" }, { nodes: [{ name: "" } as any, nodes[0], nodes[1]], listenerPorts: { "[HK] Alpha One": 42000, Other: "bad" as any } }, ); - expect(result.html).toContain("无变更"); + expect(noOpResult.html).toContain("无变更"); buttonByText("完成").onClick(); - expect(result.props.bulkRenameNodes).not.toHaveBeenCalled(); - expect(result.props.onOpenChange).toHaveBeenCalledWith(false); + expect(noOpResult.props.bulkRenameNodes).not.toHaveBeenCalled(); + expect(noOpResult.props.onOpenChange).toHaveBeenCalledWith(false); buttonByText("自动填充监听端口").onClick(); - expect(result.props.bulkSetListenerPorts).toHaveBeenCalledWith({ "[HK] Alpha One": 43000 }); + expect(noOpResult.props.bulkSetListenerPorts).toHaveBeenCalledWith({ "[HK] Alpha One": 43000 }); - result = renderDialog({ 0: "[", 6: "42000" }); + const invalidRegexResult = renderDialog({ 0: "[", 6: "42000" }); buttonByText("自动填充监听端口").onClick(); buttonByText("批量删除监听端口").onClick(); buttonByText("完成").onClick(); - expect(result.props.bulkSetListenerPorts).not.toHaveBeenCalled(); - expect(result.props.bulkRenameNodes).not.toHaveBeenCalled(); + expect(invalidRegexResult.props.bulkSetListenerPorts).not.toHaveBeenCalled(); + expect(invalidRegexResult.props.bulkRenameNodes).not.toHaveBeenCalled(); - result = renderDialog({ 0: "Missing" }); + renderDialog({ 0: "Missing" }); buttonByText("批量删除监听端口").onClick(); expect(mocks.toast).toHaveBeenCalledWith(expect.objectContaining({ title: "暂无匹配节点", variant: "warning" })); }); diff --git a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-categories.tsx b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-categories.tsx index 800f3c2..7319596 100644 --- a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-categories.tsx +++ b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-categories.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; -import { ChevronDown, ChevronRight, Pencil, RotateCcw } from "lucide-react"; +import { ChevronDown, ChevronRight, RotateCcw } from "lucide-react"; import { Badge } from "@subboost/ui/components/ui/badge"; import { confirmDialog } from "@subboost/ui/components/ui/confirm-dialog"; import { diff --git a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-groups-panel.tsx b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-groups-panel.tsx index af21a26..99d6379 100644 --- a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-groups-panel.tsx +++ b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-groups-panel.tsx @@ -38,7 +38,6 @@ import { export function ProxyGroupsCustomGroupsPanel() { const { - ruleProviderBaseUrl, enabledProxyGroups, hiddenProxyGroups, proxyGroupNameOverrides, diff --git a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-rules.tsx b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-rules.tsx index 2dd2cb3..73e9d73 100644 --- a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-rules.tsx +++ b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-custom-rules.tsx @@ -34,7 +34,6 @@ import { RULE_EDIT_TRAILING_CONTROLS_CLASS, RULE_HEADER_ACTION_BUTTON_CLASS, RULE_HEADER_ROW_CLASS, - RULE_TARGET_SELECT_TRIGGER_CLASS, RULE_TEXT_ACTION_BUTTON_CLASS, } from "./proxy-groups-rule-editor-layout"; diff --git a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-module-rules-panel.test.ts b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-module-rules-panel.test.ts index 5e62add..556919f 100644 --- a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-module-rules-panel.test.ts +++ b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-module-rules-panel.test.ts @@ -236,7 +236,7 @@ describe("ProxyGroupsModuleRulesPanel", () => { it("moves, removes, restores, and adds CN experimental and candidate rule sets", () => { const props = baseProps(); - let result = renderPanel(props, { + renderPanel(props, { 0: [{ id: "candidate-1", name: "Candidate", behavior: "domain", path: "rule-set/candidate.mrs", parentRuleId: "parent" }], }); @@ -256,8 +256,8 @@ describe("ProxyGroupsModuleRulesPanel", () => { ]); const disabledProps = baseProps({ experimentalCnUseCnRuleSet: false }); - result = renderPanel(disabledProps); - expect(result.html).toContain("已移除"); + const disabledResult = renderPanel(disabledProps); + expect(disabledResult.html).toContain("已移除"); mocks.captures.buttons.find((button) => button["aria-label"] === "恢复 中国业务实验规则 规则集").onClick(); expect(disabledProps.onChangeExperimentalCnUseCnRuleSet).toHaveBeenCalledWith(true); }); diff --git a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-rules-search.test.ts b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-rules-search.test.ts index 6f0c93a..e95e8c2 100644 --- a/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-rules-search.test.ts +++ b/packages/ui/src/product/converter/advanced-mode/sections/proxy-groups-rules-search.test.ts @@ -147,21 +147,21 @@ describe("proxy group rules library search", () => { }); it("loads total rules as helper copy", async () => { - let hook = useRenderedHook(); + useRenderedHook(); await flushAsync(); - hook = useRenderedHook(); + const hook = useRenderedHook(); expect(mocks.bag.rulesApi.getTotalRules).toHaveBeenCalled(); expect(hook.totalRules).toBe(100); }); it("searches after debounce and records success metadata", async () => { - let hook = useRenderedHook(); - hook.setRuleSearchKeyword(" openai "); - hook = useRenderedHook(); + const initialHook = useRenderedHook(); + initialHook.setRuleSearchKeyword(" openai "); + useRenderedHook(); vi.advanceTimersByTime(300); await flushAsync(); - hook = useRenderedHook(); + const hook = useRenderedHook(); expect(mocks.bag.rulesApi.searchRules).toHaveBeenCalledWith( expect.objectContaining({ keyword: "openai", page: 1, size: 50 }) @@ -192,17 +192,17 @@ describe("proxy group rules library search", () => { totalRules: 120, }); - let hook = useRenderedHook(); - hook.setRuleSearchKeyword("ai"); - hook = useRenderedHook(); + const initialHook = useRenderedHook(); + initialHook.setRuleSearchKeyword("ai"); + useRenderedHook(); vi.advanceTimersByTime(300); await flushAsync(); - hook = useRenderedHook(); + const firstPageHook = useRenderedHook(); - expect(hook.canLoadMore).toBe(true); - hook.handleLoadMore(); + expect(firstPageHook.canLoadMore).toBe(true); + firstPageHook.handleLoadMore(); await flushAsync(); - hook = useRenderedHook(); + const hook = useRenderedHook(); expect(mocks.bag.rulesApi.searchRules).toHaveBeenLastCalledWith( expect.objectContaining({ keyword: "ai", page: 2, size: 50 }) @@ -216,12 +216,12 @@ describe("proxy group rules library search", () => { getTotalRules: vi.fn(async () => null), }; - let hook = useRenderedHook(); - hook.setRuleSearchKeyword("openai"); - hook = useRenderedHook(); + const initialHook = useRenderedHook(); + initialHook.setRuleSearchKeyword("openai"); + useRenderedHook(); vi.advanceTimersByTime(300); await flushAsync(); - hook = useRenderedHook(); + const hook = useRenderedHook(); expect(hook.searchResults).toEqual([]); expect(hook.rulesSearchError).toBe("规则库接口暂不可用"); @@ -254,29 +254,29 @@ describe("proxy group rules library search", () => { }) .mockRejectedValue("boom"); - let hook = useRenderedHook(); - hook.setRuleSearchKeyword("missing"); - hook = useRenderedHook(); + const initialHook = useRenderedHook(); + initialHook.setRuleSearchKeyword("missing"); + useRenderedHook(); vi.advanceTimersByTime(300); await flushAsync(); - hook = useRenderedHook(); + const emptyResultHook = useRenderedHook(); - expect(hook.searchResults).toEqual([]); - expect(hook.totalMatched).toBeNull(); - expect(hook.rulesSearchSource).toBeNull(); + expect(emptyResultHook.searchResults).toEqual([]); + expect(emptyResultHook.totalMatched).toBeNull(); + expect(emptyResultHook.rulesSearchSource).toBeNull(); expect(mocks.bag.interactions.rulesSearchCompleted).toHaveBeenCalledWith({ result: "noResult", resultSource: "unknown", resultCount: 0, }); - hook.setRuleSearchKeyword("again"); - hook = useRenderedHook(); + emptyResultHook.setRuleSearchKeyword("again"); + useRenderedHook(); vi.advanceTimersByTime(300); await flushAsync(); - hook = useRenderedHook(); + const failedHook = useRenderedHook(); - expect(hook.rulesSearchError).toBe("搜索失败"); + expect(failedHook.rulesSearchError).toBe("搜索失败"); }); it("guards load-more calls while busy or unavailable", () => { @@ -315,23 +315,23 @@ describe("proxy group rules library search", () => { }) .mockRejectedValueOnce("offline"); - let hook = useRenderedHook(); - hook.setRuleSearchKeyword("ai"); - hook = useRenderedHook(); + const initialHook = useRenderedHook(); + initialHook.setRuleSearchKeyword("ai"); + useRenderedHook(); vi.advanceTimersByTime(300); await flushAsync(); - hook = useRenderedHook(); + const firstPageHook = useRenderedHook(); - hook.handleLoadMore(); + firstPageHook.handleLoadMore(); await flushAsync(); - hook = useRenderedHook(); - expect(hook.searchResults.map((item) => item.id)).toEqual(["a", "b"]); - expect(hook.rulesSearchSource).toBe("unavailable"); - expect(hook.totalRules).toBe(101); + const mergedHook = useRenderedHook(); + expect(mergedHook.searchResults.map((item) => item.id)).toEqual(["a", "b"]); + expect(mergedHook.rulesSearchSource).toBe("unavailable"); + expect(mergedHook.totalRules).toBe(101); - hook.handleLoadMore(); + mergedHook.handleLoadMore(); await flushAsync(); - hook = useRenderedHook(); - expect(hook.rulesSearchError).toBe("加载失败"); + const failedHook = useRenderedHook(); + expect(failedHook.rulesSearchError).toBe("加载失败"); }); }); diff --git a/packages/ui/src/product/converter/quick-mode/sources-section.tsx b/packages/ui/src/product/converter/quick-mode/sources-section.tsx index 64c4740..2dd0fd7 100644 --- a/packages/ui/src/product/converter/quick-mode/sources-section.tsx +++ b/packages/ui/src/product/converter/quick-mode/sources-section.tsx @@ -258,7 +258,6 @@ export function SourcesSection() {
{sources.map((source, index) => { const typeInfo = sourceTypeInfo[source.type]; - const TypeIcon = typeInfo.icon; const sourceDisplayLabel = buildSourceDisplayLabel({ typeLabel: typeInfo.label, tag: source.tag, diff --git a/packages/ui/src/product/converter/sections/section-header.tsx b/packages/ui/src/product/converter/sections/section-header.tsx index 097f89d..4d3212e 100644 --- a/packages/ui/src/product/converter/sections/section-header.tsx +++ b/packages/ui/src/product/converter/sections/section-header.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import { ChevronDown, ChevronRight } from "lucide-react"; -import { Badge } from "@subboost/ui/components/ui/badge"; export interface SectionHeaderProps { expanded: boolean; diff --git a/packages/ui/src/product/preview/visual-graph.tsx b/packages/ui/src/product/preview/visual-graph.tsx index 4bad338..11582fc 100644 --- a/packages/ui/src/product/preview/visual-graph.tsx +++ b/packages/ui/src/product/preview/visual-graph.tsx @@ -107,8 +107,6 @@ export function VisualGraph() { ); // 节点名称列表 - const nodeNames = React.useMemo(() => nodes.map((n) => n.name), [nodes]); - // 生成当前配置下的代理组(用于显示“默认选中项”) const generatedProxyGroups = React.useMemo(() => { if (nodes.length === 0) return []; diff --git a/packages/ui/src/store/config-store/persistence.ts b/packages/ui/src/store/config-store/persistence.ts index a2dc4d6..1eb5623 100644 --- a/packages/ui/src/store/config-store/persistence.ts +++ b/packages/ui/src/store/config-store/persistence.ts @@ -1,9 +1,6 @@ import type { ConfigState } from "./definitions"; import { safeParseJsonObject } from "@subboost/core/json"; -import { - CONFIG_DRAFT_GUEST_STORAGE_NAME, - getConfigDraftStorageNameForUser, -} from "./draft-storage"; +import { getConfigDraftStorageNameForUser } from "./draft-storage"; export { CONFIG_DRAFT_GUEST_STORAGE_NAME, diff --git a/packages/ui/src/store/config-store/source-actions.ts b/packages/ui/src/store/config-store/source-actions.ts index cca6fa5..8e9b736 100644 --- a/packages/ui/src/store/config-store/source-actions.ts +++ b/packages/ui/src/store/config-store/source-actions.ts @@ -26,7 +26,6 @@ import type { ConfigActions, SubscriptionSource } from "./definitions"; import { fetchUrlContentInBrowser, getNodeSourceIds, - makeUniqueName, ORIGIN_NAME_KEY, SOURCE_IDS_KEY, withNodeSourceId,