Compare commits

...

1 commit

Author SHA1 Message Date
GuaGua
5850c27c39 fix: normalize pasted URL patterns 2026-03-27 16:48:59 -07:00
6 changed files with 62 additions and 14 deletions

View file

@ -1,6 +1,7 @@
import { i18n } from "#imports"
import { useAtom } from "jotai"
import { configFieldsAtomMap } from "@/utils/atoms/config"
import { normalizeDomainPattern } from "@/utils/url"
import { ConfigCard } from "../../components/config-card"
import { PatternsTable } from "../../components/patterns-table"
@ -9,13 +10,13 @@ export function FloatingButtonDisabledSites() {
const { disabledFloatingButtonPatterns = [] } = floatingButtonConfig
const addPattern = (pattern: string) => {
const cleanedPattern = pattern.trim()
if (!cleanedPattern || disabledFloatingButtonPatterns.includes(cleanedPattern))
const normalizedPattern = normalizeDomainPattern(pattern)
if (!normalizedPattern || disabledFloatingButtonPatterns.includes(normalizedPattern))
return
void setFloatingButtonConfig({
...floatingButtonConfig,
disabledFloatingButtonPatterns: [...disabledFloatingButtonPatterns, cleanedPattern],
disabledFloatingButtonPatterns: [...disabledFloatingButtonPatterns, normalizedPattern],
})
}

View file

@ -3,6 +3,7 @@ import { useAtom } from "jotai"
import { Label } from "@/components/ui/base-ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/base-ui/radio-group"
import { configFieldsAtomMap } from "@/utils/atoms/config"
import { normalizeDomainPattern } from "@/utils/url"
import { ConfigCard } from "../../components/config-card"
import { PatternsTable } from "../../components/patterns-table"
@ -15,13 +16,13 @@ export default function SiteControlMode() {
const patterns = siteControl[patternsKey] ?? []
const addPattern = async (pattern: string) => {
const cleanedPattern = pattern.trim()
if (!cleanedPattern || patterns.includes(cleanedPattern))
const normalizedPattern = normalizeDomainPattern(pattern)
if (!normalizedPattern || patterns.includes(normalizedPattern))
return
await setSiteControl({
...siteControl,
[patternsKey]: [...patterns, cleanedPattern],
[patternsKey]: [...patterns, normalizedPattern],
})
}

View file

@ -1,6 +1,7 @@
import { i18n } from "#imports"
import { useAtom } from "jotai"
import { configFieldsAtomMap } from "@/utils/atoms/config"
import { normalizeDomainPattern } from "@/utils/url"
import { ConfigCard } from "../../components/config-card"
import { PatternsTable } from "../../components/patterns-table"
@ -9,13 +10,13 @@ export function SelectionToolbarDisabledSites() {
const { disabledSelectionToolbarPatterns = [] } = selectionToolbarConfig
const addPattern = (pattern: string) => {
const cleanedPattern = pattern.trim()
if (!cleanedPattern || disabledSelectionToolbarPatterns.includes(cleanedPattern))
const normalizedPattern = normalizeDomainPattern(pattern)
if (!normalizedPattern || disabledSelectionToolbarPatterns.includes(normalizedPattern))
return
void setSelectionToolbarConfig({
...selectionToolbarConfig,
disabledSelectionToolbarPatterns: [...disabledSelectionToolbarPatterns, cleanedPattern],
disabledSelectionToolbarPatterns: [...disabledSelectionToolbarPatterns, normalizedPattern],
})
}

View file

@ -1,6 +1,7 @@
import { i18n } from "#imports"
import { useAtom } from "jotai"
import { configFieldsAtomMap } from "@/utils/atoms/config"
import { normalizeDomainPattern } from "@/utils/url"
import { ConfigCard } from "../../components/config-card"
import { PatternsTable } from "../../components/patterns-table"
@ -9,14 +10,14 @@ export function AutoTranslateWebsitePatterns() {
const { autoTranslatePatterns } = translateConfig.page
const addPattern = (pattern: string) => {
const cleanedPattern = pattern.trim()
if (!cleanedPattern || autoTranslatePatterns.includes(cleanedPattern))
const normalizedPattern = normalizeDomainPattern(pattern)
if (!normalizedPattern || autoTranslatePatterns.includes(normalizedPattern))
return
void setTranslateConfig({
page: {
...translateConfig.page,
autoTranslatePatterns: [...autoTranslatePatterns, cleanedPattern],
autoTranslatePatterns: [...autoTranslatePatterns, normalizedPattern],
},
})
}

View file

@ -1,5 +1,23 @@
import { describe, expect, it } from "vitest"
import { matchDomainPattern } from "../url"
import { matchDomainPattern, normalizeDomainPattern } from "../url"
describe("normalizeDomainPattern", () => {
it("extracts the hostname from a full URL", () => {
expect(normalizeDomainPattern("https://news.ycombinator.com/")).toBe("news.ycombinator.com")
})
it("extracts the hostname from a bare hostname with a path", () => {
expect(normalizeDomainPattern("news.ycombinator.com/item?id=1")).toBe("news.ycombinator.com")
})
it("preserves a plain domain pattern", () => {
expect(normalizeDomainPattern(" Example.com ")).toBe("example.com")
})
it("returns invalid non-url input as a trimmed lowercase pattern", () => {
expect(normalizeDomainPattern(" not a valid url pattern ")).toBe("not a valid url pattern")
})
})
describe("matchDomainPattern", () => {
describe("exact domain match", () => {
@ -37,6 +55,11 @@ describe("matchDomainPattern", () => {
const result = matchDomainPattern("https://x.com", " x.com ")
expect(result).toBe(true)
})
it("should match when the pattern is a full URL", () => {
const result = matchDomainPattern("https://news.ycombinator.com/item?id=1", "https://news.ycombinator.com/")
expect(result).toBe(true)
})
})
describe("subdomain matching", () => {

View file

@ -1,5 +1,26 @@
import { z } from "zod"
export function normalizeDomainPattern(pattern: string): string {
const cleanedPattern = pattern.trim()
if (!cleanedPattern) {
return ""
}
const candidates = cleanedPattern.includes("://")
? [cleanedPattern]
: [cleanedPattern, `https://${cleanedPattern}`]
for (const candidate of candidates) {
if (!z.url().safeParse(candidate).success) {
continue
}
return new URL(candidate).hostname.toLowerCase()
}
return cleanedPattern.toLowerCase()
}
export function matchDomainPattern(url: string, pattern: string): boolean {
if (!z.url().safeParse(url).success) {
return false
@ -7,7 +28,7 @@ export function matchDomainPattern(url: string, pattern: string): boolean {
const urlObj = new URL(url)
const hostname = urlObj.hostname.toLowerCase()
const patternLower = pattern.toLowerCase().trim()
const patternLower = normalizeDomainPattern(pattern)
if (hostname === patternLower) {
return true