diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json index 3142650..9d091c6 100644 --- a/public/locales/en-US/dashboard.json +++ b/public/locales/en-US/dashboard.json @@ -437,6 +437,11 @@ "capSiteKeyDes": "The site key from your Cap server dashboard.", "capSecretKey": "Secret Key", "capSecretKeyDes": "The secret key from your Cap server dashboard.", + "capAssetServer": "Asset Server Source", + "capAssetServerDes": "Choose the source for loading Cap captcha static assets. Using self-deployed server requires setting environment variables on the server side, please refer to <0>enable asset server.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "Self-hosted server", "captchaProvider": "Captcha provider", "captchaWidth": "Width", "captchaHeight": "Height", diff --git a/public/locales/ja-JP/dashboard.json b/public/locales/ja-JP/dashboard.json index 617679c..83bba97 100644 --- a/public/locales/ja-JP/dashboard.json +++ b/public/locales/ja-JP/dashboard.json @@ -435,6 +435,11 @@ "capSiteKeyDes": "Cap サーバーダッシュボードから取得したサイトキー。", "capSecretKey": "シークレットキー", "capSecretKeyDes": "Cap サーバーダッシュボードから取得したシークレットキー。", + "capAssetServer": "静的リソースソース", + "capAssetServerDes": "Cap認証コードの静的リソースの読み込みソースを選択します。自己デプロイサーバーを使用するにはサーバー側で環境変数を設定する必要があります、<0>静的リソースサービスを有効にするを参照してください。", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "自己デプロイサーバー", "captchaProvider": "認証コードタイプ", "captchaWidth": "幅", "captchaHeight": "高さ", diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json index 37f3255..16c603f 100644 --- a/public/locales/zh-CN/dashboard.json +++ b/public/locales/zh-CN/dashboard.json @@ -430,11 +430,16 @@ "turnstileSiteKSecret": "密钥", "cap": "Cap", "capInstanceURL": "实例 URL", - "capInstanceURLDes": "自部署 Cap 服务器的 URL 地址。详细信息请参考 <0>独立模式文档。", + "capInstanceURLDes": "自部署 Cap 服务器的 URL 地址。详细信息请参考 <0>自部署模式文档。", "capSiteKey": "站点密钥", "capSiteKeyDes": "从 Cap 服务器控制面板获取的站点密钥。", "capSecretKey": "私密密钥", "capSecretKeyDes": "从 Cap 服务器控制面板获取的私密密钥。", + "capAssetServer": "静态资源服务源", + "capAssetServerDes": "选择 Cap 验证码静态资源的加载源。使用自部署服务器需要在服务器端设置环境变量,请参考 <0>开启静态资源服务。", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "自部署服务器", "captchaProvider": "验证码类型", "captchaWidth": "宽度", "captchaHeight": "高度", diff --git a/public/locales/zh-TW/dashboard.json b/public/locales/zh-TW/dashboard.json index 3805696..f9e2666 100644 --- a/public/locales/zh-TW/dashboard.json +++ b/public/locales/zh-TW/dashboard.json @@ -432,6 +432,11 @@ "capSiteKeyDes": "從 Cap 伺服器控制面板獲取的站點金鑰。", "capSecretKey": "私密金鑰", "capSecretKeyDes": "從 Cap 伺服器控制面板獲取的私密金鑰。", + "capAssetServer": "靜態資源服務源", + "capAssetServerDes": "選擇 Cap 驗證碼靜態資源的載入源。使用自部署伺服器需要在伺服器端設定環境變數,請參考 <0>開啟靜態資源服務。", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "自部署伺服器", "captchaProvider": "驗證碼型別", "captchaWidth": "寬度", "captchaHeight": "高度", diff --git a/src/api/site.ts b/src/api/site.ts index dbd8ba4..52cf4db 100644 --- a/src/api/site.ts +++ b/src/api/site.ts @@ -26,6 +26,7 @@ export interface SiteConfig { captcha_cap_instance_url?: string; captcha_cap_site_key?: string; captcha_cap_secret_key?: string; + captcha_cap_asset_server?: string; register_enabled?: boolean; logo?: string; logo_light?: string; diff --git a/src/component/Admin/Settings/Captcha/CapCaptcha.tsx b/src/component/Admin/Settings/Captcha/CapCaptcha.tsx index 907e15b..eb839f5 100644 --- a/src/component/Admin/Settings/Captcha/CapCaptcha.tsx +++ b/src/component/Admin/Settings/Captcha/CapCaptcha.tsx @@ -1,7 +1,8 @@ import { Trans, useTranslation } from "react-i18next"; -import { FormControl, Link, Stack } from "@mui/material"; +import { FormControl, Link, Stack, ListItemText } from "@mui/material"; import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; -import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx"; +import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; import * as React from "react"; import { NoMarginHelperText } from "../Settings.tsx"; @@ -78,6 +79,59 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => { + + + + setSettings({ + captcha_cap_asset_server: e.target.value, + }) + } + > + + + {t("settings.capAssetServerJsdelivr")} + + + + + {t("settings.capAssetServerUnpkg")} + + + + + {t("settings.capAssetServerInstance")} + + + + + , + ]} + /> + + + ); }; diff --git a/src/component/Admin/Settings/Settings.tsx b/src/component/Admin/Settings/Settings.tsx index f054862..4b2be9e 100644 --- a/src/component/Admin/Settings/Settings.tsx +++ b/src/component/Admin/Settings/Settings.tsx @@ -224,6 +224,7 @@ const Settings = () => { "captcha_cap_instance_url", "captcha_cap_site_key", "captcha_cap_secret_key", + "captcha_cap_asset_server", ]} > diff --git a/src/component/Common/Captcha/CapCaptcha.tsx b/src/component/Common/Captcha/CapCaptcha.tsx index 6787d8e..ec4ed50 100644 --- a/src/component/Common/Captcha/CapCaptcha.tsx +++ b/src/component/Common/Captcha/CapCaptcha.tsx @@ -4,6 +4,12 @@ import { useAppSelector } from "../../../redux/hooks.ts"; import { CaptchaParams } from "./Captcha.tsx"; import { Box, useTheme } from "@mui/material"; +// Cap Widget URLs +const CAP_WASM_UNPKG_URL = "https://unpkg.com/@cap.js/wasm@0.0.4/browser/cap_wasm.js"; +const CAP_WASM_JSDELIVR_URL = "https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.4/browser/cap_wasm.min.js"; +const CAP_WIDGET_UNPKG_URL = "https://unpkg.com/@cap.js/widget"; +const CAP_WIDGET_JSDELIVR_URL = "https://cdn.jsdelivr.net/npm/@cap.js/widget"; + export interface CapProps { onStateChange: (state: CaptchaParams) => void; generation: number; @@ -22,6 +28,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps const capInstanceURL = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_instance_url); const capSiteKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_site_key); + const capAssetServer = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_asset_server); // Keep callback reference up to date useEffect(() => { @@ -132,6 +139,17 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps const initWidget = () => { scriptLoadedRef.current = true; + + // 根据配置设置WASM URL,保持资源加载的一致性 + if (capAssetServer === "instance") { + (window as any).CAP_CUSTOM_WASM_URL = `${capInstanceURL.replace(/\/$/, "")}/assets/cap_wasm.min.js`; + } else if (capAssetServer === "unpkg") { + (window as any).CAP_CUSTOM_WASM_URL = CAP_WASM_UNPKG_URL; + } else { + // jsdelivr - 默认CDN + (window as any).CAP_CUSTOM_WASM_URL = CAP_WASM_JSDELIVR_URL; + } + // Add a small delay to ensure DOM is ready setTimeout(() => { createWidget(); @@ -141,11 +159,29 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps if (!script) { script = document.createElement("script"); script.id = scriptId; - script.src = `${capInstanceURL.replace(/\/$/, "")}/assets/widget.js`; + + // 根据配置选择静态资源源 + let assetSource; + if (capAssetServer === "instance") { + assetSource = `${capInstanceURL.replace(/\/$/, "")}/assets/widget.js`; + } else if (capAssetServer === "unpkg") { + assetSource = CAP_WIDGET_UNPKG_URL; + } else { + // jsdelivr - 默认CDN + assetSource = CAP_WIDGET_JSDELIVR_URL; + } + + script.src = assetSource; script.async = true; script.onload = initWidget; script.onerror = () => { - console.error("Failed to load Cap widget script"); + if (capAssetServer === "instance") { + console.error("Failed to load Cap widget script from instance server"); + } else if (capAssetServer === "unpkg") { + console.error("Failed to load Cap widget script from unpkg CDN"); + } else { + console.error("Failed to load Cap widget script from jsDelivr CDN"); + } }; document.head.appendChild(script); } else if (scriptLoadedRef.current || (window as any).Cap) { @@ -166,7 +202,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps captchaRef.current.innerHTML = ""; } }; - }, [capInstanceURL, capSiteKey, t]); + }, [capInstanceURL, capSiteKey, capAssetServer, t]); if (!capInstanceURL || !capSiteKey) { return null;