mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-11-04 14:45:26 +08:00
fix(webui): setup loop, reactive selects, escape handler, axios/i18n and dep bumps
- Clear needSetup after completion so the wizard stops bouncing back to itself - ab-select watches modelValue (config selects showed stale defaults); notification-type dropdown renders options - Search modal Escape handler no longer opens the modal from anywhere; ab-button defaults type=button (no double login) - axios surfaces 400/404/422; background polls suppress the network-error toast storm; calendar re-reads locally on activate - Search subscribe uses the active provider + i18n; provider settings go through axios; dead search-store code removed - Bump axios ^1.7 (CVE-2023-45857) and vue-i18n ^9.14.2 (XSS); add missing i18n keys Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh
This commit is contained in:
@@ -23,11 +23,11 @@
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@vueuse/components": "^10.11.1",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"axios": "^0.27.2",
|
||||
"axios": "^1.7.9",
|
||||
"naive-ui": "^2.39.0",
|
||||
"pinia": "^2.2.2",
|
||||
"vue": "^3.5.8",
|
||||
"vue-i18n": "^9.14.0",
|
||||
"vue-i18n": "^9.14.2",
|
||||
"vue-inline-svg": "^3.1.4",
|
||||
"vue-router": "^4.4.5",
|
||||
"vuedraggable": "^4.1.0"
|
||||
|
||||
217
webui/pnpm-lock.yaml
generated
217
webui/pnpm-lock.yaml
generated
@@ -18,8 +18,8 @@ importers:
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.5.8(typescript@4.9.5))
|
||||
axios:
|
||||
specifier: ^0.27.2
|
||||
version: 0.27.2
|
||||
specifier: ^1.7.9
|
||||
version: 1.18.1
|
||||
naive-ui:
|
||||
specifier: ^2.39.0
|
||||
version: 2.39.0(vue@3.5.8(typescript@4.9.5))
|
||||
@@ -30,8 +30,8 @@ importers:
|
||||
specifier: ^3.5.8
|
||||
version: 3.5.8(typescript@4.9.5)
|
||||
vue-i18n:
|
||||
specifier: ^9.14.0
|
||||
version: 9.14.0(vue@3.5.8(typescript@4.9.5))
|
||||
specifier: ^9.14.2
|
||||
version: 9.14.5(vue@3.5.8(typescript@4.9.5))
|
||||
vue-inline-svg:
|
||||
specifier: ^3.1.4
|
||||
version: 3.1.4(vue@3.5.8(typescript@4.9.5))
|
||||
@@ -50,7 +50,7 @@ importers:
|
||||
version: 1.4.2(vue@3.5.8(typescript@4.9.5))
|
||||
'@intlify/unplugin-vue-i18n':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(rollup@2.79.1)(vue-i18n@9.14.0(vue@3.5.8(typescript@4.9.5)))(webpack-sources@3.2.3)
|
||||
version: 0.11.0(rollup@2.79.1)(vue-i18n@9.14.5(vue@3.5.8(typescript@4.9.5)))(webpack-sources@3.2.3)
|
||||
'@storybook/addon-essentials':
|
||||
specifier: ^7.6.20
|
||||
version: 7.6.20(@types/react@18.3.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(webpack-sources@3.2.3)
|
||||
@@ -1057,20 +1057,20 @@ packages:
|
||||
vue-i18n:
|
||||
optional: true
|
||||
|
||||
'@intlify/core-base@9.14.0':
|
||||
resolution: {integrity: sha512-zJn0imh9HIsZZUtt9v8T16PeVstPv6bP2YzlrYJwoF8F30gs4brZBwW2KK6EI5WYKFi3NeqX6+UU4gniz5TkGg==}
|
||||
'@intlify/core-base@9.14.5':
|
||||
resolution: {integrity: sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/message-compiler@9.14.0':
|
||||
resolution: {integrity: sha512-sXNsoMI0YsipSXW8SR75drmVK56tnJHoYbPXUv2Cf9lz6FzvwsosFm6JtC1oQZI/kU+n7qx0qRrEWkeYFTgETA==}
|
||||
'@intlify/message-compiler@9.14.5':
|
||||
resolution: {integrity: sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/message-compiler@9.3.0-beta.17':
|
||||
resolution: {integrity: sha512-i7hvVIRk1Ax2uKa9xLRJCT57to08OhFMhFXXjWN07rmx5pWQYQ23MfX1xgggv9drnWTNhqEiD+u4EJeHoS5+Ww==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
'@intlify/shared@9.14.0':
|
||||
resolution: {integrity: sha512-r+N8KRQL7LgN1TMTs1A2svfuAU0J94Wu9wWdJVJqYsoMMLIeJxrPjazihfHpmJqfgZq0ah3Y9Q4pgWV2O90Fyg==}
|
||||
'@intlify/shared@9.14.5':
|
||||
resolution: {integrity: sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@9.3.0-beta.17':
|
||||
@@ -2307,6 +2307,10 @@ packages:
|
||||
resolution: {integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
aggregate-error@3.1.0:
|
||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2436,8 +2440,8 @@ packages:
|
||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
axios@0.27.2:
|
||||
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
||||
axios@1.18.1:
|
||||
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
|
||||
|
||||
babel-core@7.0.0-bridge.0:
|
||||
resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
|
||||
@@ -2557,6 +2561,10 @@ packages:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
call-bind@1.0.7:
|
||||
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2946,6 +2954,10 @@ packages:
|
||||
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
@@ -3008,6 +3020,10 @@ packages:
|
||||
resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3022,10 +3038,18 @@ packages:
|
||||
resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.0.3:
|
||||
resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-shim-unscopables@1.0.2:
|
||||
resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
|
||||
|
||||
@@ -3396,8 +3420,8 @@ packages:
|
||||
resolution: {integrity: sha512-WHRizzSrWFTcKo7cVcbP3wzZVhzsoYxoWqbnH4z+JXGqrjVmnsld6kBZWVlB200PwD5ur8r+HV3KUDxv3cHhOQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
follow-redirects@1.15.9:
|
||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||
follow-redirects@1.16.0:
|
||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
@@ -3416,6 +3440,10 @@ packages:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
form-data@4.0.6:
|
||||
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -3472,6 +3500,10 @@ packages:
|
||||
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-nonce@1.0.1:
|
||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3491,6 +3523,10 @@ packages:
|
||||
resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-stream@6.0.1:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3548,6 +3584,10 @@ packages:
|
||||
gopd@1.0.1:
|
||||
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
@@ -3592,6 +3632,10 @@ packages:
|
||||
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3603,6 +3647,10 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
@@ -3629,6 +3677,10 @@ packages:
|
||||
resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
@@ -4161,6 +4213,10 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>= 0.14.0'
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
md5-hex@3.0.1:
|
||||
resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4683,6 +4739,10 @@ packages:
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
proxy-from-env@2.1.0:
|
||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
pug-attrs@3.0.0:
|
||||
resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==}
|
||||
|
||||
@@ -5797,8 +5857,8 @@ packages:
|
||||
vue-component-type-helpers@2.1.6:
|
||||
resolution: {integrity: sha512-ng11B8B/ZADUMMOsRbqv0arc442q7lifSubD0v8oDXIFoMg/mXwAPUunrroIDkY+mcD0dHKccdaznSVp8EoX3w==}
|
||||
|
||||
vue-component-type-helpers@3.2.5:
|
||||
resolution: {integrity: sha512-tkvNr+bU8+xD/onAThIe7CHFvOJ/BO6XCOrxMzeytJq40nTfpGDJuVjyCM8ccGZKfAbGk2YfuZyDMXM56qheZQ==}
|
||||
vue-component-type-helpers@3.3.6:
|
||||
resolution: {integrity: sha512-FkljacAwJ9BUoSUdpFe3VDy0sGigNlTH9+2zcXUWmZOjN8swiCkl3t48wOJun0OsUd2cEIda1l04tsxMiKIIrQ==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
@@ -5822,9 +5882,10 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: '>=6.0.0'
|
||||
|
||||
vue-i18n@9.14.0:
|
||||
resolution: {integrity: sha512-LxmpRuCt2rI8gqU+kxeflRZMQn4D5+4M3oP3PWZdowW/ePJraHqhF7p4CuaME52mUxdw3Mmy2yAUKgfZYgCRjA==}
|
||||
vue-i18n@9.14.5:
|
||||
resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==}
|
||||
engines: {node: '>= 16'}
|
||||
deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
@@ -7173,7 +7234,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@intlify/bundle-utils@6.0.1(vue-i18n@9.14.0(vue@3.5.8(typescript@4.9.5)))':
|
||||
'@intlify/bundle-utils@6.0.1(vue-i18n@9.14.5(vue@3.5.8(typescript@4.9.5)))':
|
||||
dependencies:
|
||||
'@intlify/message-compiler': 9.3.0-beta.17
|
||||
'@intlify/shared': 9.3.0-beta.17
|
||||
@@ -7186,16 +7247,16 @@ snapshots:
|
||||
source-map: 0.6.1
|
||||
yaml-eslint-parser: 0.3.2
|
||||
optionalDependencies:
|
||||
vue-i18n: 9.14.0(vue@3.5.8(typescript@4.9.5))
|
||||
vue-i18n: 9.14.5(vue@3.5.8(typescript@4.9.5))
|
||||
|
||||
'@intlify/core-base@9.14.0':
|
||||
'@intlify/core-base@9.14.5':
|
||||
dependencies:
|
||||
'@intlify/message-compiler': 9.14.0
|
||||
'@intlify/shared': 9.14.0
|
||||
'@intlify/message-compiler': 9.14.5
|
||||
'@intlify/shared': 9.14.5
|
||||
|
||||
'@intlify/message-compiler@9.14.0':
|
||||
'@intlify/message-compiler@9.14.5':
|
||||
dependencies:
|
||||
'@intlify/shared': 9.14.0
|
||||
'@intlify/shared': 9.14.5
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@intlify/message-compiler@9.3.0-beta.17':
|
||||
@@ -7203,13 +7264,13 @@ snapshots:
|
||||
'@intlify/shared': 9.3.0-beta.17
|
||||
source-map: 0.6.1
|
||||
|
||||
'@intlify/shared@9.14.0': {}
|
||||
'@intlify/shared@9.14.5': {}
|
||||
|
||||
'@intlify/shared@9.3.0-beta.17': {}
|
||||
|
||||
'@intlify/unplugin-vue-i18n@0.11.0(rollup@2.79.1)(vue-i18n@9.14.0(vue@3.5.8(typescript@4.9.5)))(webpack-sources@3.2.3)':
|
||||
'@intlify/unplugin-vue-i18n@0.11.0(rollup@2.79.1)(vue-i18n@9.14.5(vue@3.5.8(typescript@4.9.5)))(webpack-sources@3.2.3)':
|
||||
dependencies:
|
||||
'@intlify/bundle-utils': 6.0.1(vue-i18n@9.14.0(vue@3.5.8(typescript@4.9.5)))
|
||||
'@intlify/bundle-utils': 6.0.1(vue-i18n@9.14.5(vue@3.5.8(typescript@4.9.5)))
|
||||
'@intlify/shared': 9.3.0-beta.17
|
||||
'@rollup/pluginutils': 5.1.1(rollup@2.79.1)
|
||||
'@vue/compiler-sfc': 3.5.8
|
||||
@@ -7222,7 +7283,7 @@ snapshots:
|
||||
source-map: 0.6.1
|
||||
unplugin: 1.14.1(webpack-sources@3.2.3)
|
||||
optionalDependencies:
|
||||
vue-i18n: 9.14.0(vue@3.5.8(typescript@4.9.5))
|
||||
vue-i18n: 9.14.5(vue@3.5.8(typescript@4.9.5))
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
@@ -8325,7 +8386,7 @@ snapshots:
|
||||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
vue: 3.5.8(typescript@4.9.5)
|
||||
vue-component-type-helpers: 3.2.5
|
||||
vue-component-type-helpers: 3.3.6
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
@@ -9009,6 +9070,12 @@ snapshots:
|
||||
|
||||
agent-base@5.1.1: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.3.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
aggregate-error@3.1.0:
|
||||
dependencies:
|
||||
clean-stack: 2.2.0
|
||||
@@ -9159,12 +9226,15 @@ snapshots:
|
||||
dependencies:
|
||||
possible-typed-array-names: 1.0.0
|
||||
|
||||
axios@0.27.2:
|
||||
axios@1.18.1:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.0
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.6
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
- supports-color
|
||||
|
||||
babel-core@7.0.0-bridge.0(@babel/core@7.25.2):
|
||||
dependencies:
|
||||
@@ -9304,6 +9374,11 @@ snapshots:
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
call-bind@1.0.7:
|
||||
dependencies:
|
||||
es-define-property: 1.0.0
|
||||
@@ -9701,6 +9776,12 @@ snapshots:
|
||||
|
||||
dotenv@16.4.5: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
duplexify@3.7.1:
|
||||
@@ -9800,6 +9881,8 @@ snapshots:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-get-iterator@1.1.3:
|
||||
@@ -9820,12 +9903,23 @@ snapshots:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.0.3:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.2
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.4
|
||||
|
||||
es-shim-unscopables@1.0.2:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
@@ -10352,7 +10446,7 @@ snapshots:
|
||||
|
||||
flow-parser@0.246.0: {}
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
follow-redirects@1.16.0: {}
|
||||
|
||||
for-each@0.3.3:
|
||||
dependencies:
|
||||
@@ -10369,6 +10463,14 @@ snapshots:
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
form-data@4.0.6:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.4
|
||||
mime-types: 2.1.35
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fresh@0.5.2: {}
|
||||
@@ -10426,6 +10528,19 @@ snapshots:
|
||||
has-symbols: 1.0.3
|
||||
hasown: 2.0.2
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.2
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.4
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
|
||||
get-npm-tarball-url@2.1.0: {}
|
||||
@@ -10436,6 +10551,11 @@ snapshots:
|
||||
|
||||
get-port@5.1.1: {}
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
get-stream@6.0.1: {}
|
||||
|
||||
get-stream@8.0.1: {}
|
||||
@@ -10511,6 +10631,8 @@ snapshots:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
graphemer@1.4.0: {}
|
||||
@@ -10560,6 +10682,8 @@ snapshots:
|
||||
|
||||
has-symbols@1.0.3: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
dependencies:
|
||||
has-symbols: 1.0.3
|
||||
@@ -10570,6 +10694,10 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
highlight.js@11.10.0: {}
|
||||
@@ -10600,6 +10728,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
@@ -11105,6 +11240,8 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
md5-hex@3.0.1:
|
||||
dependencies:
|
||||
blueimp-md5: 2.19.0
|
||||
@@ -11605,6 +11742,8 @@ snapshots:
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
pug-attrs@3.0.0:
|
||||
dependencies:
|
||||
constantinople: 4.0.1
|
||||
@@ -12827,7 +12966,7 @@ snapshots:
|
||||
|
||||
vue-component-type-helpers@2.1.6: {}
|
||||
|
||||
vue-component-type-helpers@3.2.5: {}
|
||||
vue-component-type-helpers@3.3.6: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.8(typescript@4.9.5)):
|
||||
dependencies:
|
||||
@@ -12862,10 +13001,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vue-i18n@9.14.0(vue@3.5.8(typescript@4.9.5)):
|
||||
vue-i18n@9.14.5(vue@3.5.8(typescript@4.9.5)):
|
||||
dependencies:
|
||||
'@intlify/core-base': 9.14.0
|
||||
'@intlify/shared': 9.14.0
|
||||
'@intlify/core-base': 9.14.5
|
||||
'@intlify/shared': 9.14.5
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.8(typescript@4.9.5)
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ async function autoDetectOffset() {
|
||||
offsetReason.value = result.reason;
|
||||
} catch (e) {
|
||||
console.error('Failed to detect offset:', e);
|
||||
message.error('Failed to detect offset');
|
||||
message.error(t('offset.detect_failed'));
|
||||
} finally {
|
||||
offsetLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ async function autoDetectOffset() {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to detect offset:', e);
|
||||
message.error('Failed to detect offset');
|
||||
message.error(t('offset.detect_failed'));
|
||||
} finally {
|
||||
offsetLoading.value = false;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ async function dismissReview() {
|
||||
message.success(t('offset.review_dismissed'));
|
||||
} catch (e) {
|
||||
console.error('Failed to dismiss review:', e);
|
||||
message.error('Failed to dismiss review');
|
||||
message.error(t('offset.dismiss_failed'));
|
||||
} finally {
|
||||
dismissingReview.value = false;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,16 @@ const props = withDefaults(
|
||||
size?: 'big' | 'normal' | 'small';
|
||||
link?: string | null;
|
||||
loading?: boolean;
|
||||
/** Native `type` attribute when rendered as a <button>. Defaults to
|
||||
* "button" so ab-button never triggers an implicit form submit. */
|
||||
nativeType?: 'button' | 'submit' | 'reset';
|
||||
}>(),
|
||||
{
|
||||
type: 'primary',
|
||||
size: 'normal',
|
||||
link: null,
|
||||
loading: false,
|
||||
nativeType: 'button',
|
||||
}
|
||||
);
|
||||
|
||||
@@ -34,6 +38,7 @@ const buttonSize = computed(() => {
|
||||
<Component
|
||||
:is="link !== null ? 'a' : 'button'"
|
||||
:href="link"
|
||||
:type="link !== null ? undefined : nativeType"
|
||||
class="btn"
|
||||
:class="[`btn--${type}`, buttonSize]"
|
||||
@click="$emit('click')"
|
||||
|
||||
@@ -19,9 +19,32 @@ const props = withDefaults(
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const selected = ref<SelectItem | string>(
|
||||
props.modelValue || (props.items?.[0] ?? '')
|
||||
);
|
||||
// Find an object item whose `.value` matches a plain string modelValue.
|
||||
// Lets callers bind a string v-model against object items ({id,label,value})
|
||||
// without having to round-trip the whole object.
|
||||
function findByValue(items: Array<SelectItem | string>, value: string) {
|
||||
return items.find((item) => isObject(item) && item.value === value);
|
||||
}
|
||||
|
||||
// Writable computed (instead of a ref seeded once from props.modelValue) so
|
||||
// external updates to modelValue (e.g. after async getConfig()) stay in sync.
|
||||
const selected = computed<SelectItem | string>({
|
||||
get() {
|
||||
if (isString(props.modelValue)) {
|
||||
return findByValue(props.items, props.modelValue) ?? props.modelValue;
|
||||
}
|
||||
return props.modelValue ?? props.items?.[0] ?? '';
|
||||
},
|
||||
set(item) {
|
||||
// Object item selected while the caller's v-model is a plain string:
|
||||
// emit item.value so the binding stays string <-> string.
|
||||
if (isObject(item) && isString(props.modelValue)) {
|
||||
emit('update:modelValue', item.value);
|
||||
} else {
|
||||
emit('update:modelValue', item);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const otherItems = computed(() => {
|
||||
return (
|
||||
@@ -56,10 +79,6 @@ function getLabel(item: SelectItem | string) {
|
||||
function getDisabled(item: SelectItem | string) {
|
||||
return isString(item) ? false : item.disabled;
|
||||
}
|
||||
|
||||
watch(selected, (val) => {
|
||||
emit('update:modelValue', val);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -13,6 +13,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const message = useMessage();
|
||||
const { t } = useMyI18n();
|
||||
|
||||
// Local deep copy of bangumi for editing (prevents mutation of original prop)
|
||||
const localBangumi = ref<BangumiRule>(JSON.parse(JSON.stringify(props.bangumi)));
|
||||
@@ -130,7 +131,7 @@ async function autoDetectOffset() {
|
||||
localBangumi.value.episode_offset = result.suggested_offset;
|
||||
} catch (e) {
|
||||
console.error('Failed to detect offset:', e);
|
||||
message.error('Failed to detect offset');
|
||||
message.error(t('offset.detect_failed'));
|
||||
} finally {
|
||||
offsetLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const message = useMessage();
|
||||
const { t } = useMyI18n();
|
||||
const { getAll } = useBangumiStore();
|
||||
|
||||
const {
|
||||
@@ -67,8 +68,11 @@ onBeforeUnmount(() => {
|
||||
closeSearch();
|
||||
});
|
||||
|
||||
// Close on Escape
|
||||
// Close on Escape (this component is always mounted, so the listener must
|
||||
// no-op while the modal itself is closed — otherwise Escape anywhere in the
|
||||
// app would toggle the modal open via the `close` -> toggleModal() handler).
|
||||
onKeyStroke('Escape', () => {
|
||||
if (!showModal.value) return;
|
||||
if (selectedResult.value) {
|
||||
clearSelectedResult();
|
||||
} else {
|
||||
@@ -109,20 +113,20 @@ async function handleConfirm(bangumi: BangumiRule) {
|
||||
name: bangumi.official_title,
|
||||
url: bangumi.rss_link?.[0] || '',
|
||||
aggregate: false,
|
||||
parser: 'mikan',
|
||||
parser: provider.value,
|
||||
enabled: true,
|
||||
connection_status: null,
|
||||
last_checked_at: null,
|
||||
last_error: null,
|
||||
};
|
||||
await apiDownload.subscribe(bangumi, rss);
|
||||
message.success('订阅成功');
|
||||
message.success(t('search.subscribe_success'));
|
||||
getAll();
|
||||
clearSelectedResult();
|
||||
emit('close');
|
||||
} catch (e) {
|
||||
console.error('Subscribe failed:', e);
|
||||
message.error('订阅失败');
|
||||
message.error(t('search.subscribe_failed'));
|
||||
} finally {
|
||||
subscribing.value = false;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { NotificationProviderConfig, NotificationType } from '#/config';
|
||||
import type { TupleToUnion } from '#/utils';
|
||||
import { apiNotification } from '@/api/notification';
|
||||
|
||||
const { t } = useMyI18n();
|
||||
const { t, returnUserLangText } = useMyI18n();
|
||||
const { getSettingGroup } = useConfigStore();
|
||||
|
||||
const notificationRef = getSettingGroup('notification');
|
||||
@@ -179,12 +179,15 @@ async function testProvider(index: number) {
|
||||
const response = await apiNotification.testProvider({ provider_index: index });
|
||||
testResult.value = {
|
||||
success: response.data.success,
|
||||
message: response.data.message_zh || response.data.message,
|
||||
message: returnUserLangText({
|
||||
en: response.data.message_en,
|
||||
'zh-CN': response.data.message_zh,
|
||||
}),
|
||||
};
|
||||
} catch (error: any) {
|
||||
} catch {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: error.message || 'Test failed',
|
||||
message: t('config.notification_set.test_failed'),
|
||||
};
|
||||
} finally {
|
||||
testingIndex.value = -1;
|
||||
@@ -200,12 +203,15 @@ async function testNewProvider() {
|
||||
);
|
||||
testResult.value = {
|
||||
success: response.data.success,
|
||||
message: response.data.message_zh || response.data.message,
|
||||
message: returnUserLangText({
|
||||
en: response.data.message_en,
|
||||
'zh-CN': response.data.message_zh,
|
||||
}),
|
||||
};
|
||||
} catch (error: any) {
|
||||
} catch {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: error.message || 'Test failed',
|
||||
message: t('config.notification_set.test_failed'),
|
||||
};
|
||||
} finally {
|
||||
testingIndex.value = -1;
|
||||
|
||||
@@ -7,6 +7,7 @@ interface SearchProvider {
|
||||
}
|
||||
|
||||
const { t } = useMyI18n();
|
||||
const message = useMessage();
|
||||
|
||||
// State
|
||||
const providers = ref<SearchProvider[]>([]);
|
||||
@@ -30,16 +31,16 @@ onMounted(() => {
|
||||
async function loadProviders() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await fetch('/api/v1/search/provider/config');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
providers.value = Object.entries(data).map(([name, url]) => ({
|
||||
name,
|
||||
url: url as string,
|
||||
}));
|
||||
}
|
||||
const { data } = await axios.get<Record<string, string>>(
|
||||
'api/v1/search/provider/config'
|
||||
);
|
||||
providers.value = Object.entries(data).map(([name, url]) => ({
|
||||
name,
|
||||
url,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to load providers:', error);
|
||||
message.error(t('config.search_provider_set.load_failed'));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -52,16 +53,10 @@ async function saveProviders() {
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/v1/search/provider/config', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(providerObj),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save providers');
|
||||
}
|
||||
await axios.put('api/v1/search/provider/config', providerObj);
|
||||
} catch (error) {
|
||||
console.error('Failed to save providers:', error);
|
||||
message.error(t('config.search_provider_set.save_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { markSetupComplete } from '@/router';
|
||||
|
||||
const { t } = useMyI18n();
|
||||
const setupStore = useSetupStore();
|
||||
const { accountData, downloaderData, rssData, notificationData, isLoading } =
|
||||
@@ -13,6 +15,7 @@ async function completeSetup() {
|
||||
await apiSetup.complete(request);
|
||||
message.success(t('setup.review.success'));
|
||||
setupStore.$reset();
|
||||
markSetupComplete();
|
||||
router.push({ name: 'Login' });
|
||||
} catch (e) {
|
||||
message.error(t('setup.review.failed'));
|
||||
|
||||
@@ -7,10 +7,17 @@ export const useAppInfo = createSharedComposable(() => {
|
||||
|
||||
function getStatus() {
|
||||
if (isLoggedIn.value) {
|
||||
apiProgram.status().then((res) => {
|
||||
running.value = res.status;
|
||||
version.value = res.version;
|
||||
});
|
||||
apiProgram
|
||||
.status()
|
||||
.then((res) => {
|
||||
running.value = res.status;
|
||||
version.value = res.version;
|
||||
})
|
||||
.catch(() => {
|
||||
// Errors are already surfaced (throttled) by the axios interceptor;
|
||||
// swallow here so a transient backend restart doesn't spam an
|
||||
// unhandled rejection on every 3s poll tick.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,9 @@
|
||||
"url_missing_placeholder": "URL must contain %s as search keyword placeholder",
|
||||
"default": "Default",
|
||||
"no_providers": "No search providers configured",
|
||||
"delete_confirm": "Are you sure you want to delete this provider?"
|
||||
"delete_confirm": "Are you sure you want to delete this provider?",
|
||||
"load_failed": "Failed to load search providers",
|
||||
"save_failed": "Failed to save search providers"
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
@@ -406,10 +408,14 @@
|
||||
"dismiss": "Dismiss",
|
||||
"suggestion_applied": "Offset suggestion applied",
|
||||
"no_mismatch": "No season/episode mismatch detected",
|
||||
"review_dismissed": "Review reminder dismissed"
|
||||
"review_dismissed": "Review reminder dismissed",
|
||||
"detect_failed": "Failed to detect offset",
|
||||
"dismiss_failed": "Failed to dismiss review"
|
||||
},
|
||||
"search": {
|
||||
"subscribe": "Subscribe",
|
||||
"subscribe_success": "Subscribed successfully",
|
||||
"subscribe_failed": "Subscription failed",
|
||||
"no_results": "No results found. Try different keywords.",
|
||||
"start_typing": "Enter keywords to start searching",
|
||||
"filter": {
|
||||
|
||||
@@ -100,7 +100,9 @@
|
||||
"url_missing_placeholder": "URL 必须包含 %s 作为搜索关键词占位符",
|
||||
"default": "默认",
|
||||
"no_providers": "暂无搜索源",
|
||||
"delete_confirm": "确定删除此搜索源?"
|
||||
"delete_confirm": "确定删除此搜索源?",
|
||||
"load_failed": "加载搜索源失败",
|
||||
"save_failed": "保存搜索源失败"
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
@@ -406,10 +408,14 @@
|
||||
"dismiss": "忽略",
|
||||
"suggestion_applied": "偏移量建议已应用",
|
||||
"no_mismatch": "未检测到季度/集数不匹配",
|
||||
"review_dismissed": "检查提醒已忽略"
|
||||
"review_dismissed": "检查提醒已忽略",
|
||||
"detect_failed": "检测偏移量失败",
|
||||
"dismiss_failed": "忽略检查提醒失败"
|
||||
},
|
||||
"search": {
|
||||
"subscribe": "订阅",
|
||||
"subscribe_success": "订阅成功",
|
||||
"subscribe_failed": "订阅失败",
|
||||
"no_results": "未找到相关结果,试试其他关键词",
|
||||
"start_typing": "输入关键词开始搜索",
|
||||
"filter": {
|
||||
|
||||
@@ -26,8 +26,9 @@ async function refreshCalendar() {
|
||||
}
|
||||
|
||||
onActivated(() => {
|
||||
if (refreshing.value) return;
|
||||
refreshCalendar();
|
||||
// Re-read local state on tab activation; the bangumi.tv scrape + DB write
|
||||
// in refreshCalendar() is reserved for the explicit refresh button.
|
||||
getAll();
|
||||
});
|
||||
|
||||
const DAY_KEYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] as const;
|
||||
|
||||
@@ -7,6 +7,13 @@ const router = createRouter({
|
||||
let setupChecked = false;
|
||||
let needSetup = false;
|
||||
|
||||
// Called once the setup wizard finishes, so the next navigation isn't
|
||||
// bounced back into /setup by the guard below.
|
||||
export function markSetupComplete() {
|
||||
needSetup = false;
|
||||
setupChecked = true;
|
||||
}
|
||||
|
||||
router.beforeEach(async (to) => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
const { type, url } = storeToRefs(usePlayerStore());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BangumiRule, SearchResult } from '#/bangumi';
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
|
||||
// Filter types
|
||||
export interface SearchFilters {
|
||||
@@ -24,55 +24,6 @@ export interface GroupedBangumi {
|
||||
variants: BangumiRule[];
|
||||
}
|
||||
|
||||
// Helper to parse metadata from title/bangumi
|
||||
function parseResolution(bangumi: BangumiRule): string {
|
||||
// Check dpi field first
|
||||
if (bangumi.dpi) {
|
||||
return bangumi.dpi;
|
||||
}
|
||||
// Parse from title_raw
|
||||
const title = bangumi.title_raw || '';
|
||||
const resMatch = title.match(/\b(4K|2160p|1080p|720p|480p)\b/i);
|
||||
return resMatch ? resMatch[1].toLowerCase() : '';
|
||||
}
|
||||
|
||||
function parseSubtitleType(bangumi: BangumiRule): string {
|
||||
// Check subtitle field first
|
||||
if (bangumi.subtitle) {
|
||||
const sub = bangumi.subtitle.toLowerCase();
|
||||
if (sub.includes('简') || sub.includes('chs') || sub.includes('sc')) return '简中';
|
||||
if (sub.includes('繁') || sub.includes('cht') || sub.includes('tc')) return '繁中';
|
||||
if (sub.includes('双语') || sub.includes('dual')) return '双语';
|
||||
}
|
||||
// Parse from title_raw
|
||||
const title = bangumi.title_raw || '';
|
||||
if (/简体|简中|CHS|SC/i.test(title)) return '简中';
|
||||
if (/繁體|繁中|CHT|TC/i.test(title)) return '繁中';
|
||||
if (/双语|Dual/i.test(title)) return '双语';
|
||||
if (/内嵌|内封/i.test(title)) return '内嵌';
|
||||
if (/外挂|ASS|SRT/i.test(title)) return '外挂';
|
||||
return '';
|
||||
}
|
||||
|
||||
function parseSeason(bangumi: BangumiRule): string {
|
||||
if (bangumi.season_raw) {
|
||||
return bangumi.season_raw;
|
||||
}
|
||||
if (bangumi.season) {
|
||||
return `S${bangumi.season}`;
|
||||
}
|
||||
// Parse from title
|
||||
const title = bangumi.title_raw || '';
|
||||
if (/剧场版|Movie|劇場版/i.test(title)) return '剧场版';
|
||||
if (/OVA/i.test(title)) return 'OVA';
|
||||
const seasonMatch = title.match(/S(\d+)|Season\s*(\d+)|第(\d+)季/i);
|
||||
if (seasonMatch) {
|
||||
const num = seasonMatch[1] || seasonMatch[2] || seasonMatch[3];
|
||||
return `S${num}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export const useSearchStore = defineStore('search', () => {
|
||||
const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
|
||||
|
||||
@@ -90,23 +41,9 @@ export const useSearchStore = defineStore('search', () => {
|
||||
// Modal state
|
||||
const showModal = ref(false);
|
||||
const selectedResult = ref<BangumiRule | null>(null);
|
||||
const selectedGroup = ref<GroupedBangumi | null>(null);
|
||||
|
||||
// Filter state for selected group variants
|
||||
const variantFilters = ref<SearchFilters>({
|
||||
group: [],
|
||||
resolution: [],
|
||||
subtitleType: [],
|
||||
season: [],
|
||||
});
|
||||
|
||||
const loading = computed(() => status.value !== 'CLOSED');
|
||||
|
||||
// Raw bangumi list with order
|
||||
const bangumiList = computed<SearchResult[]>(() =>
|
||||
searchData.value.map((item, index) => ({ order: index, value: item }))
|
||||
);
|
||||
|
||||
// Group results by official_title
|
||||
const groupedResults = computed<GroupedBangumi[]>(() => {
|
||||
const map = new Map<string, BangumiRule[]>();
|
||||
@@ -135,86 +72,6 @@ export const useSearchStore = defineStore('search', () => {
|
||||
return groups;
|
||||
});
|
||||
|
||||
// Extract filter options from selected group's variants
|
||||
const variantFilterOptions = computed<FilterOptions>(() => {
|
||||
if (!selectedGroup.value) {
|
||||
return { group: [], resolution: [], subtitleType: [], season: [] };
|
||||
}
|
||||
|
||||
const groups = new Set<string>();
|
||||
const resolutions = new Set<string>();
|
||||
const subtitleTypes = new Set<string>();
|
||||
const seasons = new Set<string>();
|
||||
|
||||
for (const item of selectedGroup.value.variants) {
|
||||
if (item.group_name) groups.add(item.group_name);
|
||||
|
||||
const res = parseResolution(item);
|
||||
if (res) resolutions.add(res);
|
||||
|
||||
const subType = parseSubtitleType(item);
|
||||
if (subType) subtitleTypes.add(subType);
|
||||
|
||||
const season = parseSeason(item);
|
||||
if (season) seasons.add(season);
|
||||
}
|
||||
|
||||
return {
|
||||
group: Array.from(groups).sort(),
|
||||
resolution: Array.from(resolutions).sort((a, b) => {
|
||||
const order = ['4k', '2160p', '1080p', '720p', '480p'];
|
||||
return order.indexOf(a.toLowerCase()) - order.indexOf(b.toLowerCase());
|
||||
}),
|
||||
subtitleType: Array.from(subtitleTypes),
|
||||
season: Array.from(seasons).sort(),
|
||||
};
|
||||
});
|
||||
|
||||
// Filtered variants based on selected filters
|
||||
const filteredVariants = computed<BangumiRule[]>(() => {
|
||||
if (!selectedGroup.value) return [];
|
||||
|
||||
const hasFilters = Object.values(variantFilters.value).some((arr) => arr.length > 0);
|
||||
if (!hasFilters) {
|
||||
return selectedGroup.value.variants;
|
||||
}
|
||||
|
||||
return selectedGroup.value.variants.filter((bangumi) => {
|
||||
// Group filter
|
||||
if (variantFilters.value.group.length > 0) {
|
||||
if (!bangumi.group_name || !variantFilters.value.group.includes(bangumi.group_name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolution filter
|
||||
if (variantFilters.value.resolution.length > 0) {
|
||||
const res = parseResolution(bangumi);
|
||||
if (!res || !variantFilters.value.resolution.includes(res)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Subtitle type filter
|
||||
if (variantFilters.value.subtitleType.length > 0) {
|
||||
const subType = parseSubtitleType(bangumi);
|
||||
if (!subType || !variantFilters.value.subtitleType.includes(subType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Season filter
|
||||
if (variantFilters.value.season.length > 0) {
|
||||
const season = parseSeason(bangumi);
|
||||
if (!season || !variantFilters.value.season.includes(season)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
async function getProviders() {
|
||||
providers.value = await apiSearch.getProvider();
|
||||
provider.value = providers.value[0];
|
||||
@@ -241,35 +98,9 @@ export const useSearchStore = defineStore('search', () => {
|
||||
function clearSearch() {
|
||||
keyword.value = '';
|
||||
searchData.value = [];
|
||||
variantFilters.value = { group: [], resolution: [], subtitleType: [], season: [] };
|
||||
selectedGroup.value = null;
|
||||
closeSearch();
|
||||
}
|
||||
|
||||
function toggleVariantFilter(category: keyof SearchFilters, value: string) {
|
||||
const arr = variantFilters.value[category];
|
||||
const index = arr.indexOf(value);
|
||||
if (index === -1) {
|
||||
arr.push(value);
|
||||
} else {
|
||||
arr.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function clearVariantFilters() {
|
||||
variantFilters.value = { group: [], resolution: [], subtitleType: [], season: [] };
|
||||
}
|
||||
|
||||
function selectGroup(group: GroupedBangumi) {
|
||||
selectedGroup.value = group;
|
||||
variantFilters.value = { group: [], resolution: [], subtitleType: [], season: [] };
|
||||
}
|
||||
|
||||
function clearSelectedGroup() {
|
||||
selectedGroup.value = null;
|
||||
variantFilters.value = { group: [], resolution: [], subtitleType: [], season: [] };
|
||||
}
|
||||
|
||||
function selectResult(bangumi: BangumiRule) {
|
||||
selectedResult.value = bangumi;
|
||||
}
|
||||
@@ -291,12 +122,7 @@ export const useSearchStore = defineStore('search', () => {
|
||||
loading,
|
||||
provider,
|
||||
providers,
|
||||
bangumiList,
|
||||
groupedResults,
|
||||
selectedGroup,
|
||||
variantFilters,
|
||||
variantFilterOptions,
|
||||
filteredVariants,
|
||||
showModal,
|
||||
selectedResult,
|
||||
|
||||
@@ -308,10 +134,6 @@ export const useSearchStore = defineStore('search', () => {
|
||||
openModal,
|
||||
closeModal,
|
||||
toggleModal,
|
||||
toggleVariantFilter,
|
||||
clearVariantFilters,
|
||||
selectGroup,
|
||||
clearSelectedGroup,
|
||||
selectResult,
|
||||
clearSelectedResult,
|
||||
};
|
||||
|
||||
@@ -6,6 +6,20 @@ export const axios = Axios.create({
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
// Throttle repeated identical error toasts (e.g. background polling during a
|
||||
// backend restart would otherwise fire the same "Network error" toast once
|
||||
// per failed request and produce a toast storm).
|
||||
const recentToastAt = new Map<string, number>();
|
||||
const TOAST_THROTTLE_MS = 4000;
|
||||
|
||||
function showErrorThrottled(msg: string) {
|
||||
const now = Date.now();
|
||||
const last = recentToastAt.get(msg) ?? 0;
|
||||
if (now - last < TOAST_THROTTLE_MS) return;
|
||||
recentToastAt.set(msg, now);
|
||||
useMessage().error(msg);
|
||||
}
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(res: AxiosResponse) => res,
|
||||
(err: AxiosError<ApiSuccess>) => {
|
||||
@@ -13,7 +27,6 @@ axios.interceptors.response.use(
|
||||
const msg_en = err.response?.data?.msg_en ?? '';
|
||||
const msg_zh = err.response?.data?.msg_zh ?? '';
|
||||
|
||||
const message = useMessage();
|
||||
const { returnUserLangText } = useMyI18n();
|
||||
|
||||
const errorMsg = returnUserLangText({
|
||||
@@ -25,7 +38,7 @@ axios.interceptors.response.use(
|
||||
|
||||
// Handle network errors (no response from server)
|
||||
if (!err.response) {
|
||||
message.error(
|
||||
showErrorThrottled(
|
||||
returnUserLangText({
|
||||
en: 'Network error. Please check your connection.',
|
||||
'zh-CN': '网络错误,请检查连接。',
|
||||
@@ -43,15 +56,15 @@ axios.interceptors.response.use(
|
||||
/** token 过期 - only logout on auth errors */
|
||||
case 401:
|
||||
isLoggedIn.value = false;
|
||||
if (errorMsg) message.error(errorMsg);
|
||||
if (errorMsg) showErrorThrottled(errorMsg);
|
||||
break;
|
||||
/** 执行失败 */
|
||||
case 406:
|
||||
if (errorMsg) message.error(errorMsg);
|
||||
if (errorMsg) showErrorThrottled(errorMsg);
|
||||
break;
|
||||
/** 服务器错误 - don't logout, just show error */
|
||||
case 500:
|
||||
message.error(
|
||||
showErrorThrottled(
|
||||
errorMsg ||
|
||||
returnUserLangText({
|
||||
en: 'Server error. Please try again later.',
|
||||
@@ -59,6 +72,17 @@ axios.interceptors.response.use(
|
||||
})
|
||||
);
|
||||
break;
|
||||
/** Anything else (400/404/422/...) - still surface it instead of
|
||||
* failing silently */
|
||||
default:
|
||||
showErrorThrottled(
|
||||
errorMsg ||
|
||||
returnUserLangText({
|
||||
en: 'Request failed. Please try again.',
|
||||
'zh-CN': '请求失败,请重试。',
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
const error = {
|
||||
|
||||
Reference in New Issue
Block a user