feat: add copy button functionality to OAuthMethodCard

- Implemented a new button to copy the user code in the OAuthMethodCard, enhancing user experience.
- Added styles for the copy button to match the UI design.
- Updated localization files to include new strings for the copy action in both English and Chinese.
This commit is contained in:
leookun
2026-08-30 21:15:03 +08:00
parent 3a2d47954e
commit 5e405b31b2
6 changed files with 136 additions and 81 deletions
@@ -46,6 +46,20 @@
letter-spacing: 0.08em; letter-spacing: 0.08em;
cursor: pointer; cursor: pointer;
} }
button.copy {
padding: 6px 2px;
color: var(--vscode-textLink-foreground);
background: none;
border: none;
font-family: inherit;
letter-spacing: normal;
font-size: type.$font-size-xs;
&:hover {
text-decoration: underline;
}
}
} }
.fileButton { .fileButton {
@@ -56,8 +56,15 @@ function OAuthMethodCard({ pluginId, resourceType, method, onConfigured }: {
const [status, setStatus] = useState<"idle" | "starting" | "polling" | "success" | "error">("idle"); const [status, setStatus] = useState<"idle" | "starting" | "polling" | "success" | "error">("idle");
const [begun, setBegun] = useState<PluginOAuthBegin | null>(null); const [begun, setBegun] = useState<PluginOAuthBegin | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const stopped = useRef(false); const stopped = useRef(false);
const copyCode = async (code: string) => {
await api.copyCursorText(code).catch(() => undefined);
setCopied(true);
window.setTimeout(() => setCopied(false), 2000);
};
useEffect(() => () => { stopped.current = true; }, []); useEffect(() => () => { stopped.current = true; }, []);
useEffect(() => { useEffect(() => {
@@ -115,7 +122,10 @@ function OAuthMethodCard({ pluginId, resourceType, method, onConfigured }: {
{method.description && <span>{pluginText(method.description, locale)}</span>} {method.description && <span>{pluginText(method.description, locale)}</span>}
{begun && status === "polling" && <div className={styles.deviceCode}> {begun && status === "polling" && <div className={styles.deviceCode}>
<small>{t("设备验证码")}</small> <small>{t("设备验证码")}</small>
<button type="button" onClick={() => void api.copyCursorText(begun.userCode)}>{begun.userCode}</button> <button type="button" onClick={() => void copyCode(begun.userCode)}>{begun.userCode}</button>
<button type="button" className={styles.copy} onClick={() => void copyCode(begun.userCode)}>
{copied ? t("已复制") : t("复制")}
</button>
</div>} </div>}
<div className={styles.actions}> <div className={styles.actions}>
<Button variant="primary" disabled={status === "starting" || status === "polling"} onClick={() => void start()}> <Button variant="primary" disabled={status === "starting" || status === "polling"} onClick={() => void start()}>
+94 -77
View File
@@ -47,7 +47,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 186, "line": 196,
"column": 39 "column": 39
} }
] ]
@@ -78,7 +78,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 233, "line": 243,
"column": 16 "column": 16
} }
] ]
@@ -107,12 +107,12 @@
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 151, "line": 151,
"column": 66 "column": 64
}, },
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 265, "line": 265,
"column": 85 "column": 70
}, },
{ {
"file": "features/settings/ProxySettingsCard.tsx", "file": "features/settings/ProxySettingsCard.tsx",
@@ -165,7 +165,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 259, "line": 310,
"column": 21 "column": 21
} }
] ]
@@ -225,8 +225,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 183, "line": 187,
"column": 14 "column": 12
} }
] ]
}, },
@@ -289,7 +289,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 186, "line": 196,
"column": 93 "column": 93
} }
] ]
@@ -650,7 +650,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 117, "line": 124,
"column": 15 "column": 15
} }
] ]
@@ -878,7 +878,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 243, "line": 294,
"column": 122 "column": 122
} }
] ]
@@ -969,7 +969,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 122, "line": 132,
"column": 73 "column": 73
} }
] ]
@@ -996,7 +996,7 @@
}, },
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 231, "line": 274,
"column": 70 "column": 70
}, },
{ {
@@ -1168,12 +1168,12 @@
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 153, "line": 153,
"column": 100 "column": 96
}, },
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 267, "line": 267,
"column": 119 "column": 102
}, },
{ {
"file": "features/models/CursorSettingsPage.tsx", "file": "features/models/CursorSettingsPage.tsx",
@@ -1182,7 +1182,7 @@
}, },
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 261, "line": 271,
"column": 68 "column": 68
} }
] ]
@@ -1194,7 +1194,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 271, "line": 281,
"column": 81 "column": 81
} }
] ]
@@ -1206,12 +1206,12 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 218, "line": 228,
"column": 32 "column": 32
}, },
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 218, "line": 228,
"column": 56 "column": 56
} }
] ]
@@ -1223,7 +1223,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 126, "line": 136,
"column": 64 "column": 64
} }
] ]
@@ -1325,8 +1325,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 190, "line": 213,
"column": 16 "column": 22
} }
] ]
}, },
@@ -1522,7 +1522,7 @@
}, },
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 231, "line": 274,
"column": 80 "column": 80
} }
] ]
@@ -1668,7 +1668,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 122, "line": 132,
"column": 92 "column": 92
} }
] ]
@@ -1772,17 +1772,17 @@
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 150, "line": 150,
"column": 88 "column": 86
}, },
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 173, "line": 173,
"column": 88 "column": 86
}, },
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 264, "line": 264,
"column": 107 "column": 92
}, },
{ {
"file": "features/models/CursorSettingsPage.tsx", "file": "features/models/CursorSettingsPage.tsx",
@@ -1798,7 +1798,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 230, "line": 273,
"column": 12 "column": 12
} }
] ]
@@ -1863,7 +1863,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 273, "line": 283,
"column": 42 "column": 42
} }
] ]
@@ -2164,7 +2164,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 223, "line": 266,
"column": 7 "column": 7
} }
] ]
@@ -2315,12 +2315,17 @@
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 152, "line": 152,
"column": 71 "column": 64
}, },
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 266, "line": 266,
"column": 90 "column": 70
},
{
"file": "features/plugins/PluginResourcePanels.tsx",
"line": 127,
"column": 30
} }
] ]
}, },
@@ -2331,7 +2336,7 @@
"refs": [ "refs": [
{ {
"file": "shared/api.ts", "file": "shared/api.ts",
"line": 485, "line": 486,
"column": 43 "column": 43
} }
] ]
@@ -2495,17 +2500,17 @@
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 150, "line": 150,
"column": 100 "column": 98
}, },
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 173, "line": 173,
"column": 100 "column": 98
}, },
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 264, "line": 264,
"column": 119 "column": 104
} }
] ]
}, },
@@ -2658,7 +2663,7 @@
"refs": [ "refs": [
{ {
"file": "shared/api.ts", "file": "shared/api.ts",
"line": 480, "line": 481,
"column": 43 "column": 43
} }
] ]
@@ -2694,7 +2699,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 258, "line": 309,
"column": 31 "column": 31
} }
] ]
@@ -2788,7 +2793,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 254, "line": 264,
"column": 13 "column": 13
} }
] ]
@@ -2800,8 +2805,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 179, "line": 182,
"column": 34 "column": 36
} }
] ]
}, },
@@ -2812,7 +2817,7 @@
"refs": [ "refs": [
{ {
"file": "shared/api.ts", "file": "shared/api.ts",
"line": 417, "line": 418,
"column": 21 "column": 21
} }
] ]
@@ -2854,7 +2859,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 243, "line": 294,
"column": 20 "column": 20
} }
] ]
@@ -2919,7 +2924,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 192, "line": 202,
"column": 31 "column": 31
} }
] ]
@@ -2970,7 +2975,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 255, "line": 306,
"column": 32 "column": 32
} }
] ]
@@ -2994,7 +2999,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 234, "line": 244,
"column": 110 "column": 110
}, },
{ {
@@ -3086,6 +3091,18 @@
} }
] ]
}, },
"8f6f8d979c981ced": {
"source": "已复制",
"kind": "text",
"placeholders": [],
"refs": [
{
"file": "features/plugins/PluginResourcePanels.tsx",
"line": 127,
"column": 19
}
]
},
"8f9b0d6cc477d334": { "8f9b0d6cc477d334": {
"source": "控制 Cursor TAB 相关接口的连接方式。", "source": "控制 Cursor TAB 相关接口的连接方式。",
"kind": "text", "kind": "text",
@@ -3143,8 +3160,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 187, "line": 200,
"column": 88 "column": 18
} }
] ]
}, },
@@ -3179,7 +3196,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 257, "line": 308,
"column": 31 "column": 31
} }
] ]
@@ -3203,7 +3220,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 192, "line": 202,
"column": 18 "column": 18
} }
] ]
@@ -3275,7 +3292,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 124, "line": 134,
"column": 147 "column": 147
} }
] ]
@@ -3319,7 +3336,7 @@
}, },
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 225, "line": 268,
"column": 9 "column": 9
} }
] ]
@@ -3381,7 +3398,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 256, "line": 307,
"column": 30 "column": 30
} }
] ]
@@ -3400,7 +3417,7 @@
}, },
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 79, "line": 86,
"column": 22 "column": 22
} }
] ]
@@ -3837,7 +3854,7 @@
}, },
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 260, "line": 270,
"column": 84 "column": 84
}, },
{ {
@@ -3893,7 +3910,7 @@
}, },
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 246, "line": 297,
"column": 70 "column": 70
} }
] ]
@@ -4102,7 +4119,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 268, "line": 278,
"column": 81 "column": 81
} }
] ]
@@ -4208,7 +4225,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 231, "line": 274,
"column": 45 "column": 45
} }
] ]
@@ -4263,7 +4280,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 229, "line": 239,
"column": 66 "column": 66
} }
] ]
@@ -4328,7 +4345,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 232, "line": 242,
"column": 102 "column": 102
}, },
{ {
@@ -4492,8 +4509,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 200, "line": 229,
"column": 20 "column": 32
} }
] ]
}, },
@@ -4569,7 +4586,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 188, "line": 198,
"column": 32 "column": 32
} }
] ]
@@ -4617,7 +4634,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 247, "line": 298,
"column": 44 "column": 44
} }
] ]
@@ -4740,8 +4757,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 179, "line": 182,
"column": 23 "column": 25
} }
] ]
}, },
@@ -4777,7 +4794,7 @@
{ {
"file": "features/models/CursorModelCards.tsx", "file": "features/models/CursorModelCards.tsx",
"line": 174, "line": 174,
"column": 70 "column": 64
}, },
{ {
"file": "features/settings/SettingsPage.tsx", "file": "features/settings/SettingsPage.tsx",
@@ -4925,7 +4942,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 122, "line": 132,
"column": 34 "column": 34
} }
] ]
@@ -4937,7 +4954,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 87, "line": 94,
"column": 36 "column": 36
} }
] ]
@@ -5040,7 +5057,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginResourcePanels.tsx", "file": "features/plugins/PluginResourcePanels.tsx",
"line": 188, "line": 198,
"column": 43 "column": 43
} }
] ]
@@ -5158,8 +5175,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 188, "line": 206,
"column": 90 "column": 20
} }
] ]
}, },
@@ -5286,8 +5303,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 194, "line": 219,
"column": 32 "column": 44
} }
] ]
}, },
@@ -5403,7 +5420,7 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 238, "line": 283,
"column": 23 "column": 23
} }
] ]
@@ -5449,7 +5466,7 @@
}, },
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 254, "line": 305,
"column": 29 "column": 29
} }
] ]
@@ -5505,8 +5522,8 @@
"refs": [ "refs": [
{ {
"file": "features/plugins/PluginManagementPage.tsx", "file": "features/plugins/PluginManagementPage.tsx",
"line": 194, "line": 219,
"column": 45 "column": 57
} }
] ]
}, },
+1
View File
@@ -213,6 +213,7 @@
"8d0c47eb9eac2d34": "Call type", "8d0c47eb9eac2d34": "Call type",
"8df48894086d6fbd": "Reason (optional)", "8df48894086d6fbd": "Reason (optional)",
"8e2d04638a11a7cb": "Only determines the request and response format; it does not change the request URL.", "8e2d04638a11a7cb": "Only determines the request and response format; it does not change the request URL.",
"8f6f8d979c981ced": "Copied",
"8f9b0d6cc477d334": "Choose how Cursor connects to TAB endpoints.", "8f9b0d6cc477d334": "Choose how Cursor connects to TAB endpoints.",
"90800c48a1dd0655": "{label} must be a JSON object", "90800c48a1dd0655": "{label} must be a JSON object",
"919cb0ce0c8db4e7": "Leave blank to keep the current password", "919cb0ce0c8db4e7": "Leave blank to keep the current password",
+1
View File
@@ -213,6 +213,7 @@
"8d0c47eb9eac2d34": "调用类型", "8d0c47eb9eac2d34": "调用类型",
"8df48894086d6fbd": "原因(可选)", "8df48894086d6fbd": "原因(可选)",
"8e2d04638a11a7cb": "只决定请求与响应的格式,不会改变请求地址。", "8e2d04638a11a7cb": "只决定请求与响应的格式,不会改变请求地址。",
"8f6f8d979c981ced": "已复制",
"8f9b0d6cc477d334": "控制 Cursor TAB 相关接口的连接方式。", "8f9b0d6cc477d334": "控制 Cursor TAB 相关接口的连接方式。",
"90800c48a1dd0655": "{label} 必须是 JSON 对象", "90800c48a1dd0655": "{label} 必须是 JSON 对象",
"919cb0ce0c8db4e7": "留空表示保留当前密码", "919cb0ce0c8db4e7": "留空表示保留当前密码",
+15 -3
View File
@@ -207,7 +207,13 @@ impl PluginWorker {
async fn stdin(&self) -> Result<Arc<Mutex<ChildStdin>>> { async fn stdin(&self) -> Result<Arc<Mutex<ChildStdin>>> {
let mut process = self.inner.process.lock().await; let mut process = self.inner.process.lock().await;
let dead = match process.as_mut() { let dead = match process.as_mut() {
Some(current) => current.child.try_wait()?.is_some(), Some(current) => current
.child
.try_wait()
.map_err(|error| {
Error::Config(format!("cannot check plugin worker status: {error}"))
})?
.is_some(),
None => true, None => true,
}; };
if dead { if dead {
@@ -368,8 +374,14 @@ async fn write_message(stdin: &Arc<Mutex<ChildStdin>>, message: &HostMessage<'_>
let mut bytes = serde_json::to_vec(message)?; let mut bytes = serde_json::to_vec(message)?;
bytes.push(b'\n'); bytes.push(b'\n');
let mut stdin = stdin.lock().await; let mut stdin = stdin.lock().await;
stdin.write_all(&bytes).await?; stdin
stdin.flush().await?; .write_all(&bytes)
.await
.map_err(|error| Error::Config(format!("cannot write to plugin worker: {error}")))?;
stdin
.flush()
.await
.map_err(|error| Error::Config(format!("cannot flush plugin worker stdin: {error}")))?;
Ok(()) Ok(())
} }