mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-29 06:00:55 +02:00
refactor: better proxy clipboard autofill
This commit is contained in:
@@ -23,7 +23,8 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { translateBackendError } from "@/lib/backend-errors";
|
||||
import type { StoredProxy } from "@/types";
|
||||
import { pickParsedProxy } from "@/lib/proxy-string";
|
||||
import type { ProxyParseResult, StoredProxy } from "@/types";
|
||||
import { RippleButton } from "./ui/ripple";
|
||||
|
||||
interface ProxyFormData {
|
||||
@@ -202,6 +203,48 @@ export function ProxyFormDialog({
|
||||
}
|
||||
}, [isSubmitting, onClose]);
|
||||
|
||||
// Proxies are copied around as one string — `socks5://user:pass@host:1080`,
|
||||
// `host:1080:user:pass`, and a dozen variants of both — so a paste into any
|
||||
// one field is almost never meant for that field alone. Hand the clipboard to
|
||||
// the same Rust parser the import dialog uses and spread the result across
|
||||
// the form. The default paste is left alone until the answer comes back, so a
|
||||
// string that isn't a proxy (a hostname, a port) lands where it was dropped.
|
||||
const handleProxyPaste = useCallback(
|
||||
(event: React.ClipboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const content = event.clipboardData.getData("text").trim();
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Captured before the browser applies the paste, so a proxy string
|
||||
// dropped into the empty name field names the proxy after its endpoint
|
||||
// instead of keeping the raw line.
|
||||
const nameBeforePaste = form.name.trim();
|
||||
|
||||
void invoke<ProxyParseResult[]>("parse_txt_proxies", { content })
|
||||
.then((results) => {
|
||||
const parsed = pickParsedProxy(results);
|
||||
if (!parsed) {
|
||||
return;
|
||||
}
|
||||
setForm((previous) => ({
|
||||
...previous,
|
||||
name: nameBeforePaste || `${parsed.host}:${parsed.port}`,
|
||||
proxy_type: parsed.proxy_type,
|
||||
host: parsed.host,
|
||||
port: parsed.port,
|
||||
username: parsed.username ?? "",
|
||||
password: parsed.password ?? "",
|
||||
vless_uri: parsed.vless_uri ?? "",
|
||||
}));
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error("Failed to parse pasted proxy:", error);
|
||||
});
|
||||
},
|
||||
[form.name],
|
||||
);
|
||||
|
||||
const isVless = form.proxy_type === "vless";
|
||||
const vlessEndpoint = isVless ? parseVlessEndpoint(form.vless_uri) : null;
|
||||
|
||||
@@ -259,6 +302,7 @@ export function ProxyFormDialog({
|
||||
onChange={(e) => {
|
||||
setForm({ ...form, name: e.target.value });
|
||||
}}
|
||||
onPaste={handleProxyPaste}
|
||||
placeholder={t("proxies.form.namePlaceholder")}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
@@ -303,6 +347,7 @@ export function ProxyFormDialog({
|
||||
onChange={(e) => {
|
||||
setForm({ ...form, vless_uri: e.target.value });
|
||||
}}
|
||||
onPaste={handleProxyPaste}
|
||||
placeholder={t("proxies.form.vlessUriPlaceholder")}
|
||||
disabled={isSubmitting}
|
||||
aria-invalid={hasInvalidVlessUri}
|
||||
@@ -337,6 +382,7 @@ export function ProxyFormDialog({
|
||||
onChange={(e) => {
|
||||
setForm({ ...form, host: e.target.value });
|
||||
}}
|
||||
onPaste={handleProxyPaste}
|
||||
placeholder={t("proxies.form.hostPlaceholder")}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
@@ -354,6 +400,7 @@ export function ProxyFormDialog({
|
||||
port: Number.parseInt(e.target.value, 10) || 0,
|
||||
});
|
||||
}}
|
||||
onPaste={handleProxyPaste}
|
||||
placeholder={t("proxies.form.portPlaceholder")}
|
||||
min="1"
|
||||
max="65535"
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { StepTransition } from "@/components/ui/step-transition";
|
||||
import { getCurrentOS } from "@/lib/browser-utils";
|
||||
import { resolveAmbiguousProxyLine } from "@/lib/proxy-string";
|
||||
import type {
|
||||
ParsedProxyLine,
|
||||
ProxyImportResult,
|
||||
@@ -265,31 +266,12 @@ export function ProxyImportDialog({ isOpen, onClose }: ProxyImportDialogProps) {
|
||||
);
|
||||
|
||||
const handleResolveAmbiguous = useCallback(() => {
|
||||
// Convert ambiguous proxies to parsed based on selected format
|
||||
const resolved: ParsedProxyLine[] = ambiguousProxies
|
||||
.filter((p) => p.selectedFormat)
|
||||
.map((p) => {
|
||||
const parts = p.line.split(":");
|
||||
if (p.selectedFormat === "host:port:username:password") {
|
||||
return {
|
||||
proxy_type: "http",
|
||||
host: parts[0],
|
||||
port: Number.parseInt(parts[1], 10),
|
||||
username: parts[2],
|
||||
password: parts[3],
|
||||
original_line: p.line,
|
||||
};
|
||||
}
|
||||
// username:password:host:port
|
||||
return {
|
||||
proxy_type: "http",
|
||||
host: parts[2],
|
||||
port: Number.parseInt(parts[3], 10),
|
||||
username: parts[0],
|
||||
password: parts[1],
|
||||
original_line: p.line,
|
||||
};
|
||||
});
|
||||
const resolved = ambiguousProxies.flatMap((p) => {
|
||||
const parsed = p.selectedFormat
|
||||
? resolveAmbiguousProxyLine(p.line, p.selectedFormat)
|
||||
: null;
|
||||
return parsed ? [parsed] : [];
|
||||
});
|
||||
|
||||
setParsedProxies((prev) => [...prev, ...resolved]);
|
||||
setStep("preview");
|
||||
|
||||
Reference in New Issue
Block a user