소스 검색

feat(sub): add Copy All Configs button to subscription page (#5163)

* feat(sub): add Copy All Configs button to subscription page

* fix(sub): include links in copyAll dependency array

Co-authored-by: Copilot Autofix powered by AI <[email protected]>

* chore: fmt

* fix(sub): drop module-level links from copyAll deps to satisfy exhaustive-deps

links is derived from window.__SUB_PAGE_DATA__ at module scope, so listing it in the useCallback dependency array triggers a react-hooks/exhaustive-deps warning (outer-scope value). Matches the existing single-link copy callback's deps.

---------

Co-authored-by: nikan <nikan>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Sanaei <[email protected]>
Nikan Zeyaei 23 시간 전
부모
커밋
ffde2f7ebf

+ 19 - 0
frontend/src/pages/sub/SubPage.tsx

@@ -111,6 +111,13 @@ export default function SubPage() {
     if (ok) messageApi.success(t('copied'));
   }, [t, messageApi]);
 
+  const copyAll = useCallback(async () => {
+    if (links.length === 0) return;
+    const allLinks = links.join('\n');
+    const ok = await ClipboardManager.copyText(allLinks);
+    if (ok) messageApi.success(t('subscription.copyAllConfigsCopied'));
+  }, [t, messageApi]);
+
   const open = useCallback((url: string) => {
     if (!url) return;
     window.open(url, '_blank');
@@ -393,6 +400,18 @@ export default function SubPage() {
                   <>
                     <Divider>{t('pages.inbounds.copyLink')}</Divider>
                     <div className="links-section">
+                      <div className="sub-link-row">
+                        <span className="sub-link-title">{t('subscription.copyAllConfigs')}</span>
+                        <div className="sub-link-actions">
+                          <Button
+                            size="small"
+                            icon={<CopyOutlined />}
+                            onClick={copyAll}
+                            aria-label={t('subscription.copyAllConfigs')}
+                            title={t('subscription.copyAllConfigs')}
+                          />
+                        </div>
+                      </div>
                       {links.map((link, idx) => {
                         const parts = parseLinkParts(link, linkEmails[idx] || '');
                         const fallback = `Link ${idx + 1}`;

+ 3 - 1
internal/web/translation/ar-EG.json

@@ -95,7 +95,9 @@
     "active": "نشط",
     "inactive": "غير نشط",
     "unlimited": "غير محدود",
-    "noExpiry": "بدون انتهاء"
+    "noExpiry": "بدون انتهاء",
+    "copyAllConfigs": "نسخ جميع الإعدادات",
+    "copyAllConfigsCopied": "تم نسخ جميع الإعدادات"
   },
   "menu": {
     "theme": "الثيم",

+ 3 - 1
internal/web/translation/en-US.json

@@ -95,7 +95,9 @@
     "active": "Active",
     "inactive": "Inactive",
     "unlimited": "Unlimited",
-    "noExpiry": "No expiry"
+    "noExpiry": "No expiry",
+    "copyAllConfigs": "Copy All Configs",
+    "copyAllConfigsCopied": "All configs copied"
   },
   "menu": {
     "theme": "Theme",

+ 3 - 1
internal/web/translation/es-ES.json

@@ -95,7 +95,9 @@
     "active": "Activo",
     "inactive": "Inactivo",
     "unlimited": "Ilimitado",
-    "noExpiry": "Sin caducidad"
+    "noExpiry": "Sin caducidad",
+    "copyAllConfigs": "Copiar Todas las Configuraciones",
+    "copyAllConfigsCopied": "Todas las configuraciones copiadas"
   },
   "menu": {
     "theme": "Tema",

+ 3 - 1
internal/web/translation/fa-IR.json

@@ -95,7 +95,9 @@
     "active": "فعال",
     "inactive": "غیرفعال",
     "unlimited": "نامحدود",
-    "noExpiry": "بدون انقضا"
+    "noExpiry": "بدون انقضا",
+    "copyAllConfigs": "کپی همه کانفیگ‌ها",
+    "copyAllConfigsCopied": "همه کانفیگ‌ها کپی شدند"
   },
   "menu": {
     "theme": "تم",

+ 3 - 1
internal/web/translation/id-ID.json

@@ -95,7 +95,9 @@
     "active": "Aktif",
     "inactive": "Nonaktif",
     "unlimited": "Tanpa batas",
-    "noExpiry": "Tanpa kedaluwarsa"
+    "noExpiry": "Tanpa kedaluwarsa",
+    "copyAllConfigs": "Salin Semua Konfigurasi",
+    "copyAllConfigsCopied": "Semua konfigurasi tersalin"
   },
   "menu": {
     "theme": "Tema",

+ 3 - 1
internal/web/translation/ja-JP.json

@@ -95,7 +95,9 @@
     "active": "有効",
     "inactive": "無効",
     "unlimited": "無制限",
-    "noExpiry": "期限なし"
+    "noExpiry": "期限なし",
+    "copyAllConfigs": "すべての設定をコピー",
+    "copyAllConfigsCopied": "すべての設定をコピーしました"
   },
   "menu": {
     "theme": "テーマ",

+ 3 - 1
internal/web/translation/pt-BR.json

@@ -95,7 +95,9 @@
     "active": "Ativo",
     "inactive": "Inativo",
     "unlimited": "Ilimitado",
-    "noExpiry": "Sem validade"
+    "noExpiry": "Sem validade",
+    "copyAllConfigs": "Copiar Todas as Configurações",
+    "copyAllConfigsCopied": "Todas as configurações copiadas"
   },
   "menu": {
     "theme": "Tema",

+ 3 - 1
internal/web/translation/ru-RU.json

@@ -95,7 +95,9 @@
     "active": "Активна",
     "inactive": "Неактивна",
     "unlimited": "Неограниченно",
-    "noExpiry": "Бессрочно"
+    "noExpiry": "Бессрочно",
+    "copyAllConfigs": "Копировать все конфигурации",
+    "copyAllConfigsCopied": "Все конфигурации скопированы"
   },
   "menu": {
     "theme": "Тема",

+ 3 - 1
internal/web/translation/tr-TR.json

@@ -95,7 +95,9 @@
     "active": "Aktif",
     "inactive": "Pasif",
     "unlimited": "Sınırsız",
-    "noExpiry": "Süresiz"
+    "noExpiry": "Süresiz",
+    "copyAllConfigs": "Tüm Yapılandırmaları Kopyala",
+    "copyAllConfigsCopied": "Tüm yapılandırmalar kopyalandı"
   },
   "menu": {
     "theme": "Tema",

+ 3 - 1
internal/web/translation/uk-UA.json

@@ -95,7 +95,9 @@
     "active": "Активна",
     "inactive": "Неактивна",
     "unlimited": "Безліміт",
-    "noExpiry": "Без строку"
+    "noExpiry": "Без строку",
+    "copyAllConfigs": "Копіювати всі конфігурації",
+    "copyAllConfigsCopied": "Всі конфігурації скопійовано"
   },
   "menu": {
     "theme": "Тема",

+ 3 - 1
internal/web/translation/vi-VN.json

@@ -95,7 +95,9 @@
     "active": "Hoạt động",
     "inactive": "Không hoạt động",
     "unlimited": "Không giới hạn",
-    "noExpiry": "Không hết hạn"
+    "noExpiry": "Không hết hạn",
+    "copyAllConfigs": "Sao chép tất cả cấu hình",
+    "copyAllConfigsCopied": "Đã sao chép tất cả cấu hình"
   },
   "menu": {
     "theme": "Chủ đề",

+ 3 - 1
internal/web/translation/zh-CN.json

@@ -95,7 +95,9 @@
     "active": "启用",
     "inactive": "停用",
     "unlimited": "无限制",
-    "noExpiry": "无到期"
+    "noExpiry": "无到期",
+    "copyAllConfigs": "复制全部配置",
+    "copyAllConfigsCopied": "已复制全部配置"
   },
   "menu": {
     "theme": "主题",

+ 3 - 1
internal/web/translation/zh-TW.json

@@ -95,7 +95,9 @@
     "active": "啟用",
     "inactive": "停用",
     "unlimited": "無限制",
-    "noExpiry": "無到期"
+    "noExpiry": "無到期",
+    "copyAllConfigs": "複製全部配置",
+    "copyAllConfigsCopied": "已複製全部配置"
   },
   "menu": {
     "theme": "主題",