diff --git a/.github/workflows/frontend-test.yml b/.github/workflows/frontend-test.yml index 043176ec..99d53b4e 100644 --- a/.github/workflows/frontend-test.yml +++ b/.github/workflows/frontend-test.yml @@ -17,6 +17,52 @@ concurrency: cancel-in-progress: true jobs: + format: + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: plugins.v2/subscribeassistantenhanced/frontend + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: '24' + cache: yarn + cache-dependency-path: plugins.v2/subscribeassistantenhanced/frontend/yarn.lock + + - name: Install dependencies + run: yarn --frozen-lockfile + + - name: Check formatting + run: yarn format:check + + lint: + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: plugins.v2/subscribeassistantenhanced/frontend + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: '24' + cache: yarn + cache-dependency-path: plugins.v2/subscribeassistantenhanced/frontend/yarn.lock + + - name: Install dependencies + run: yarn --frozen-lockfile + + - name: Lint + run: yarn lint + unit-tests: runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/plugins.v2/subscribeassistantenhanced/frontend/.prettierignore b/plugins.v2/subscribeassistantenhanced/frontend/.prettierignore new file mode 100644 index 00000000..007ea8a7 --- /dev/null +++ b/plugins.v2/subscribeassistantenhanced/frontend/.prettierignore @@ -0,0 +1,3 @@ +dist +node_modules +coverage diff --git a/plugins.v2/subscribeassistantenhanced/frontend/.prettierrc.json b/plugins.v2/subscribeassistantenhanced/frontend/.prettierrc.json new file mode 100644 index 00000000..a7f0d2d4 --- /dev/null +++ b/plugins.v2/subscribeassistantenhanced/frontend/.prettierrc.json @@ -0,0 +1,29 @@ +{ + "arrowParens": "avoid", + "bracketSameLine": false, + "bracketSpacing": true, + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": true, + "printWidth": 120, + "proseWrap": "preserve", + "quoteProps": "preserve", + "requirePragma": false, + "semi": false, + "singleAttributePerLine": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false, + "vueIndentScriptAndStyle": false, + "overrides": [ + { + "files": ["src/config/defaults.ts", "src/config/fields.ts"], + "options": { + "singleQuote": false, + "trailingComma": "none" + } + } + ] +} diff --git a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-Cp4ctAFt.js b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-C8q1BGHX.js similarity index 92% rename from plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-Cp4ctAFt.js rename to plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-C8q1BGHX.js index ca940acc..942b5a14 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-Cp4ctAFt.js +++ b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-C8q1BGHX.js @@ -55,9 +55,7 @@ const configDefaults = { "tv_no_download_days": 180, "no_download_actions": [], "site_total_probe_enabled": false, - "paused_probe_reasons": [ - "no_download" - ], + "paused_probe_reasons": ["no_download"], "paused_probe_min_pause_days": 14, "paused_probe_interval_hours": 72, "best_version_type": "no", @@ -154,13 +152,40 @@ function useConfigDraft(initialConfig) { const groups = [ { key: "global", title: "全局运行", icon: "mdi-tune-variant", summary: "插件开关、通知、一次性动作与公共周期" }, - { key: "cleanup", title: "订阅清理", icon: "mdi-delete-sweep-outline", summary: "下载监控、删种、Tracker 与整理记录清理", highRisk: true }, + { + key: "cleanup", + title: "订阅清理", + icon: "mdi-delete-sweep-outline", + summary: "下载监控、删种、Tracker 与整理记录清理", + highRisk: true + }, { key: "pending", title: "订阅待定", icon: "mdi-timer-sand", summary: "下载中与剧集目标未稳定时保持待定" }, - { key: "pause", title: "订阅暂停", icon: "mdi-pause-circle-outline", summary: "按用户、上映播出窗口和无下载策略暂停订阅" }, + { + key: "pause", + title: "订阅暂停", + icon: "mdi-pause-circle-outline", + summary: "按用户、上映播出窗口和无下载策略暂停订阅" + }, { key: "completion", title: "订阅补全", icon: "mdi-radar", summary: "站点集数探测与暂停订阅补搜" }, - { key: "bestVersion", title: "订阅洗版", icon: "mdi-auto-fix", summary: "洗版范围、时限、回填和分集转全集", highRisk: true }, - { key: "guard", title: "完结信号", icon: "mdi-shield-check-outline", summary: "完结守卫、站点证据、波动节奏和自动纠错" }, - { key: "recognition", title: "识别增强", icon: "mdi-account-search-outline", summary: "候选准入、通知、二次识别和自定义策略" } + { + key: "bestVersion", + title: "订阅洗版", + icon: "mdi-auto-fix", + summary: "洗版范围、时限、回填和分集转全集", + highRisk: true + }, + { + key: "guard", + title: "完结信号", + icon: "mdi-shield-check-outline", + summary: "完结守卫、站点证据、波动节奏和自动纠错" + }, + { + key: "recognition", + title: "识别增强", + icon: "mdi-account-search-outline", + summary: "候选准入、通知、二次识别和自定义策略" + } ]; const fields = [ { @@ -1162,84 +1187,234 @@ function t(localeSource, key, params = {}) { const locale = normalizeLocale(localeSource); const template = messages[locale][key] ?? messages["zh-CN"][key]; if (!template) throw new Error(`Missing translation key: ${key}`); - return template.replace(/\{(\w+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match); + return template.replace( + /\{(\w+)\}/g, + (match, name) => Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match + ); } const groupTranslations = { - global: { tw: ["全域執行", "外掛開關、通知、單次操作與共用週期"], en: ["General", "Plugin state, notifications, one-time actions, and shared schedules"] }, - cleanup: { tw: ["訂閱清理", "下載監控、刪除種子、Tracker 與整理記錄清理"], en: ["Cleanup", "Download monitoring, torrent removal, Tracker rules, and history cleanup"] }, - pending: { tw: ["訂閱待定", "下載中或集數目標尚未穩定時保持待定"], en: ["Pending", "Keep subscriptions pending while downloads or episode targets are unsettled"] }, - pause: { tw: ["訂閱暫停", "依使用者、播出窗口與無下載策略暫停訂閱"], en: ["Pause", "Pause subscriptions by user, release window, or no-download policy"] }, - completion: { tw: ["訂閱補全", "站點集數探測與暫停訂閱補搜"], en: ["Completion", "Site episode probes and paused subscription searches"] }, - bestVersion: { tw: ["訂閱洗版", "洗版範圍、時限、回填與分集轉全集"], en: ["Best version", "Upgrade scope, time limits, backfill, and episode-to-season conversion"] }, - guard: { tw: ["完結訊號", "完結守衛、站點證據、波動節奏與自動修正"], en: ["Completion guard", "Completion checks, site evidence, cadence, and automatic correction"] }, - recognition: { tw: ["識別增強", "候選准入、通知、二次識別與自訂策略"], en: ["Recognition", "Candidate checks, notifications, re-identification, and custom policies"] } + global: { + tw: ["全域執行", "外掛開關、通知、單次操作與共用週期"], + en: ["General", "Plugin state, notifications, one-time actions, and shared schedules"] + }, + cleanup: { + tw: ["訂閱清理", "下載監控、刪除種子、Tracker 與整理記錄清理"], + en: ["Cleanup", "Download monitoring, torrent removal, Tracker rules, and history cleanup"] + }, + pending: { + tw: ["訂閱待定", "下載中或集數目標尚未穩定時保持待定"], + en: ["Pending", "Keep subscriptions pending while downloads or episode targets are unsettled"] + }, + pause: { + tw: ["訂閱暫停", "依使用者、播出窗口與無下載策略暫停訂閱"], + en: ["Pause", "Pause subscriptions by user, release window, or no-download policy"] + }, + completion: { + tw: ["訂閱補全", "站點集數探測與暫停訂閱補搜"], + en: ["Completion", "Site episode probes and paused subscription searches"] + }, + bestVersion: { + tw: ["訂閱洗版", "洗版範圍、時限、回填與分集轉全集"], + en: ["Best version", "Upgrade scope, time limits, backfill, and episode-to-season conversion"] + }, + guard: { + tw: ["完結訊號", "完結守衛、站點證據、波動節奏與自動修正"], + en: ["Completion guard", "Completion checks, site evidence, cadence, and automatic correction"] + }, + recognition: { + tw: ["識別增強", "候選准入、通知、二次識別與自訂策略"], + en: ["Recognition", "Candidate checks, notifications, re-identification, and custom policies"] + } }; const englishFields = { enabled: ["Enable plugin", "Activate the plugin and register its scheduled tasks"], notify: ["Send notifications", "Send notifications when relevant events occur"], onlyonce: ["Run once now", "Run a full inspection after saving, then reset automatically"], - reset_task: ["Reset data", "Reset all pending, paused, and monitored task data after saving, then reset automatically"], - auto_check_interval_minutes: ["General check interval (minutes)", "Interval for site sampling, pending release, no-download handling, and cleanup"], + reset_task: [ + "Reset data", + "Reset all pending, paused, and monitored task data after saving, then reset automatically" + ], + auto_check_interval_minutes: [ + "General check interval (minutes)", + "Interval for site sampling, pending release, no-download handling, and cleanup" + ], download_check_interval_minutes: ["Download check interval (minutes)", "How often download task status is checked"], meta_check_interval_hours: ["Metadata check interval (hours)", "How often subscription metadata is reviewed"], best_version_cron: ["Best-version schedule", "CRON schedule for best-version checks, for example 0 15 * * *"], download_monitor_enabled: ["Remove stalled downloads", "Automatically remove subscription torrents that time out"], manual_delete_listen: ["Watch manual torrent removal", "Record torrents manually removed by the user"], - tracker_response_listen: ["Watch Tracker response keywords", "Remove torrents when a configured Tracker response keyword matches"], + tracker_response_listen: [ + "Watch Tracker response keywords", + "Remove torrents when a configured Tracker response keyword matches" + ], auto_search_when_delete: ["Search after removal", "Trigger a completion search after removing a torrent"], skip_deletion: ["Skip recently removed releases", "Avoid downloading recently removed torrents again"], - download_timeout_minutes: ["Download timeout (minutes)", "Observation window used to detect downloads with insufficient progress"], - download_progress_threshold: ["Download progress threshold", "Remove only when progress increases by less than N% during the timeout window"], - download_queue_grace_multiplier: ["Download queue grace multiplier", "Allow N additional timeout windows while the downloader explicitly reports the task as queued; 0 disables the grace period"], - download_retry_limit: ["Consecutive timeout limit", "Keep the torrent and notify after N consecutive low-progress timeouts"], + download_timeout_minutes: [ + "Download timeout (minutes)", + "Observation window used to detect downloads with insufficient progress" + ], + download_progress_threshold: [ + "Download progress threshold", + "Remove only when progress increases by less than N% during the timeout window" + ], + download_queue_grace_multiplier: [ + "Download queue grace multiplier", + "Allow N additional timeout windows while the downloader explicitly reports the task as queued; 0 disables the grace period" + ], + download_retry_limit: [ + "Consecutive timeout limit", + "Keep the torrent and notify after N consecutive low-progress timeouts" + ], delete_exclude_tags: ["Excluded tags", "Comma-separated tags that must not be processed"], - default_tracker_response: ["Tracker response keywords", "One keyword per line; case-insensitive regular expressions are supported"], - delete_record_retention_hours: ["Removal history retention (hours)", "Periodically remove deletion records older than N hours"], - subscription_cleanup_history_type: ["Cleanup media scope", "Media types whose old transfer records and files are removed before download"], - subscription_cleanup_history_scenes: ["Cleanup trigger scenarios", "Choose which subscription download scenarios trigger cleanup"], - recognition_guard_mode: ["Recognition mode", "Review whether a candidate matches the subscription target before automatic download"], + default_tracker_response: [ + "Tracker response keywords", + "One keyword per line; case-insensitive regular expressions are supported" + ], + delete_record_retention_hours: [ + "Removal history retention (hours)", + "Periodically remove deletion records older than N hours" + ], + subscription_cleanup_history_type: [ + "Cleanup media scope", + "Media types whose old transfer records and files are removed before download" + ], + subscription_cleanup_history_scenes: [ + "Cleanup trigger scenarios", + "Choose which subscription download scenarios trigger cleanup" + ], + recognition_guard_mode: [ + "Recognition mode", + "Review whether a candidate matches the subscription target before automatic download" + ], recognition_guard_notify: ["Recognition notifications", "Control recognition messages without affecting audit logs"], - recognition_guard_notify_interval: ["Notification rate limit (seconds)", "Minimum interval for the same subscription, action, and reason"], + recognition_guard_notify_interval: [ + "Notification rate limit (seconds)", + "Minimum interval for the same subscription, action, and reason" + ], recognition_guard_tmdb_recheck_mode: ["Secondary recognition", "Control when secondary recognition is performed"], - recognition_guard_cache_maxsize: ["Recognition cache size", "Cache secondary recognition results to avoid duplicate requests"], - recognition_guard_custom_config: ["Custom recognition rules", "Edit only when built-in rules are insufficient; leave empty to inherit the current mode"], - pending_enhanced_enabled: ["Automatically pend TV subscriptions", "Mark TV subscriptions pending to avoid completing them too early"], + recognition_guard_cache_maxsize: [ + "Recognition cache size", + "Cache secondary recognition results to avoid duplicate requests" + ], + recognition_guard_custom_config: [ + "Custom recognition rules", + "Edit only when built-in rules are insufficient; leave empty to inherit the current mode" + ], + pending_enhanced_enabled: [ + "Automatically pend TV subscriptions", + "Mark TV subscriptions pending to avoid completing them too early" + ], pending_download_enabled: ["Pend active downloads", "Keep subscriptions pending while downloads are in progress"], auto_tv_pending_days: ["TV pending days", "Keep pending before the release date plus N days; 0 disables this rule"], - auto_tv_pending_episodes: ["TV pending episode count", "Keep pending when the episode count is at or below this value; 0 disables this rule"], - pending_use_volatility: ["Use change rate for pending", "Pend early when the total episode count changes near completion"], + auto_tv_pending_episodes: [ + "TV pending episode count", + "Keep pending when the episode count is at or below this value; 0 disables this rule" + ], + pending_use_volatility: [ + "Use change rate for pending", + "Pend early when the total episode count changes near completion" + ], pause_enhanced_enabled: ["Automatically pause subscriptions", "Pause subscriptions to avoid unnecessary requests"], - auto_pause_users: ["Auto-pause users (comma-separated)", "Pause new subscriptions from listed users; leave empty to disable"], - airing_pause_days: ["Upcoming episode pause days", "Pause when the next episode is more than N days away; 0 disables this rule"], - movie_air_pause_days: ["Movie release pause days", "Pause until N days before the movie release date; 0 disables this rule"], + auto_pause_users: [ + "Auto-pause users (comma-separated)", + "Pause new subscriptions from listed users; leave empty to disable" + ], + airing_pause_days: [ + "Upcoming episode pause days", + "Pause when the next episode is more than N days away; 0 disables this rule" + ], + movie_air_pause_days: [ + "Movie release pause days", + "Pause until N days before the movie release date; 0 disables this rule" + ], tv_air_pause_days: ["TV premiere pause days", "Pause until N days before the TV premiere date; 0 disables this rule"], - movie_no_download_days: ["Movie no-download days", "Apply the selected policy when no movie download occurs within N days; 0 disables it"], - tv_no_download_days: ["TV no-download days", "Apply the selected policy when no TV download occurs within N days; 0 disables it"], + movie_no_download_days: [ + "Movie no-download days", + "Apply the selected policy when no movie download occurs within N days; 0 disables it" + ], + tv_no_download_days: [ + "TV no-download days", + "Apply the selected policy when no TV download occurs within N days; 0 disables it" + ], no_download_actions: ["No-download actions", "Choose the actions to apply when no download is found"], - site_total_probe_enabled: ["Probe site episode totals", "Use cached site releases to detect an incomplete episode target"], + site_total_probe_enabled: [ + "Probe site episode totals", + "Use cached site releases to detect an incomplete episode target" + ], paused_probe_reasons: ["Paused search scenarios", "Choose pause reasons that allow low-frequency searches"], - paused_probe_min_pause_days: ["Search after N paused days", "Start searching after this many paused days; 0 disables it"], - paused_probe_interval_hours: ["Search interval (hours)", "Minimum interval between two searches for the same subscription"], - best_version_type: ["Best-version type", "Select media types for automatic upgrades; Off disables creation and checks"], - best_version_movie_remaining_days: ["Movie upgrade time limit (days)", "Stop movie upgrade subscriptions after this period; 0 means unlimited"], - best_version_tv_remaining_days: ["TV upgrade time limit (days)", "Stop TV upgrade subscriptions after this period; 0 means unlimited"], - best_version_episode_to_full: ["Convert episodes to full season", "Switch from episode upgrades to a full-season upgrade when the target is met"], - best_version_backfill_enabled: ["Backfill existing episodes", "Backfill library episodes when creating or converting an episode upgrade"], - backfill_best_version_now: ["Scan and backfill now", "Backfill existing episode-upgrade subscriptions after saving, then reset automatically"], - completion_guard_mode: ["Completion guard mode", "Choose the review strength used before completion; Balanced is the default"], - site_completion_evidence_enabled: ["Use site completion evidence", "Use site release titles as supporting completion evidence"], + paused_probe_min_pause_days: [ + "Search after N paused days", + "Start searching after this many paused days; 0 disables it" + ], + paused_probe_interval_hours: [ + "Search interval (hours)", + "Minimum interval between two searches for the same subscription" + ], + best_version_type: [ + "Best-version type", + "Select media types for automatic upgrades; Off disables creation and checks" + ], + best_version_movie_remaining_days: [ + "Movie upgrade time limit (days)", + "Stop movie upgrade subscriptions after this period; 0 means unlimited" + ], + best_version_tv_remaining_days: [ + "TV upgrade time limit (days)", + "Stop TV upgrade subscriptions after this period; 0 means unlimited" + ], + best_version_episode_to_full: [ + "Convert episodes to full season", + "Switch from episode upgrades to a full-season upgrade when the target is met" + ], + best_version_backfill_enabled: [ + "Backfill existing episodes", + "Backfill library episodes when creating or converting an episode upgrade" + ], + backfill_best_version_now: [ + "Scan and backfill now", + "Backfill existing episode-upgrade subscriptions after saving, then reset automatically" + ], + completion_guard_mode: [ + "Completion guard mode", + "Choose the review strength used before completion; Balanced is the default" + ], + site_completion_evidence_enabled: [ + "Use site completion evidence", + "Use site release titles as supporting completion evidence" + ], volatility_enabled: ["Episode-count change signal", "Treat recent total episode count changes as unstable"], volatility_window_days: ["Change-rate window (days)", "Number of days used to measure total episode count changes"], - cadence_enabled: ["Airing cadence signal", "Estimate the waiting period from airing intervals without directly marking completion"], + cadence_enabled: [ + "Airing cadence signal", + "Estimate the waiting period from airing intervals without directly marking completion" + ], cadence_multiplier: ["Cadence window multiplier", "Increase the estimated waiting period; higher values wait longer"], - cadence_min_window_days: ["Minimum cadence window (days)", "The estimated waiting period cannot be shorter than this value"], - cadence_min_episodes: ["Minimum episodes for cadence", "Calculate airing intervals only after this many episodes have aired"], + cadence_min_window_days: [ + "Minimum cadence window (days)", + "The estimated waiting period cannot be shorter than this value" + ], + cadence_min_episodes: [ + "Minimum episodes for cadence", + "Calculate airing intervals only after this many episodes have aired" + ], season_cooldown_days: ["Season cooldown (days)", "Continue observing for this many days after the last episode airs"], - verify_enabled: ["Automatic correction", "Recheck completed episode counts and rebuild subscriptions when the count increases"], + verify_enabled: [ + "Automatic correction", + "Recheck completed episode counts and rebuild subscriptions when the count increases" + ], verify_interval_hours: ["Correction interval (hours)", "Interval for rechecking episode counts after completion"], - verify_retention_days: ["Snapshot retention (days)", "Retain completion snapshots for this many days; default is 180"], - timeout_release_days: ["Pre-completion observation days", "Maximum number of days allowed for pre-completion observation"], - timeout_cadence_acceleration: ["Accelerate release by cadence", "Shorten the observation period after the cadence waiting window ends"] + verify_retention_days: [ + "Snapshot retention (days)", + "Retain completion snapshots for this many days; default is 180" + ], + timeout_release_days: [ + "Pre-completion observation days", + "Maximum number of days allowed for pre-completion observation" + ], + timeout_cadence_acceleration: [ + "Accelerate release by cadence", + "Shorten the observation period after the cadence waiting window ends" + ] }; const traditionalPhrases = [ ["插件", "外掛"], @@ -1494,7 +1669,8 @@ function localizedOptionTitle(locale, field, value, source) { if (locale === "zh-CN") return source; if (locale === "zh-TW") return toTraditional(source); if (typeof value === "number") { - if (field.key === "auto_check_interval_minutes" || field.key === "download_check_interval_minutes") return `${value} minutes`; + if (field.key === "auto_check_interval_minutes" || field.key === "download_check_interval_minutes") + return `${value} minutes`; if (field.key === "meta_check_interval_hours") return `${value} hours`; return String(value); } @@ -1586,7 +1762,20 @@ function numberFieldUnit(key, locale = "zh-CN") { } function displayFieldLabel(field) { if (field.kind !== "number") return field.label; - return field.label.replace(/\s*[((][^))]+[))]\s*/g, "").trim(); + let label = field.label; + for (const [opening, closing] of [ + ["(", ")"], + ["(", ")"] + ]) { + let start = label.indexOf(opening); + while (start >= 0) { + const end = label.indexOf(closing, start + opening.length); + if (end < 0) break; + label = `${label.slice(0, start)}${label.slice(end + closing.length)}`; + start = label.indexOf(opening); + } + } + return label.trim(); } const {defineComponent:_defineComponent} = await importShared('vue'); @@ -1691,16 +1880,18 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({ const locale = computed(() => normalizeLocale(instance?.appContext.config.globalProperties.$i18n?.locale)); const localizedGroups = computed(() => localizeGroups(locale.value, groups)); const localizedFields = computed(() => localizeFields(locale.value, fields)); - const fieldsByKey = computed(() => new Map( - localizedFields.value.filter((field) => !field.legacyUiKey && !field.dialogOnly).map((field) => [field.key, field]) - )); - const trackerField = computed(() => localizedFields.value.find( - (field) => field.key === "default_tracker_response" && field.dialogOnly - )); - const yamlField = computed(() => localizedFields.value.find( - (field) => field.key === "recognition_guard_custom_config" - )); - const changedItems = computed(() => changedKeys.value.slice(0, 3).map((key) => localizedFields.value.find((field) => field.key === key)).filter((field) => Boolean(field))); + const fieldsByKey = computed( + () => new Map( + localizedFields.value.filter((field) => !field.legacyUiKey && !field.dialogOnly).map((field) => [field.key, field]) + ) + ); + const trackerField = computed( + () => localizedFields.value.find((field) => field.key === "default_tracker_response" && field.dialogOnly) + ); + const yamlField = computed(() => localizedFields.value.find((field) => field.key === "recognition_guard_custom_config")); + const changedItems = computed( + () => changedKeys.value.slice(0, 3).map((key) => localizedFields.value.find((field) => field.key === key)).filter((field) => Boolean(field)) + ); const hiddenChangedCount = computed(() => Math.max(0, changedKeys.value.length - changedItems.value.length)); const activeGroup = ref("global"); const runtimeSummary = ref(null); @@ -1786,29 +1977,17 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({ { titleKey: "section.siteProbe", keys: ["site_total_probe_enabled"] }, { titleKey: "section.pausedProbe", - keys: [ - "paused_probe_reasons", - "paused_probe_min_pause_days", - "paused_probe_interval_hours" - ] + keys: ["paused_probe_reasons", "paused_probe_min_pause_days", "paused_probe_interval_hours"] } ], bestVersion: [ { titleKey: "section.bestVersionScope", - keys: [ - "best_version_type", - "best_version_movie_remaining_days", - "best_version_tv_remaining_days" - ] + keys: ["best_version_type", "best_version_movie_remaining_days", "best_version_tv_remaining_days"] }, { titleKey: "section.backfill", - keys: [ - "best_version_episode_to_full", - "best_version_backfill_enabled", - "backfill_best_version_now" - ] + keys: ["best_version_episode_to_full", "best_version_backfill_enabled", "backfill_best_version_now"] } ], guard: [ @@ -2713,6 +2892,6 @@ const _export_sfc = (sfc, props) => { return target; }; -const Config = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-a840fd50"]]); +const Config = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9b738b9d"]]); export { Config as default }; diff --git a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-DByuQ3DF.css b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-CnHfUIf2.css similarity index 64% rename from plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-DByuQ3DF.css rename to plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-CnHfUIf2.css index cc101127..591492c2 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-DByuQ3DF.css +++ b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/__federation_expose_Config-CnHfUIf2.css @@ -1,15 +1,15 @@ -.sae-config[data-v-a840fd50] { +.sae-config[data-v-9b738b9d] { container-type: inline-size; min-inline-size: 0; color: rgb(var(--v-theme-on-surface)); letter-spacing: 0; } -.sae-config[data-v-a840fd50], -.sae-config[data-v-a840fd50] * { +.sae-config[data-v-9b738b9d], +.sae-config[data-v-9b738b9d] * { box-sizing: border-box; } -.sae-config__form[data-v-a840fd50] { +.sae-config__form[data-v-9b738b9d] { min-inline-size: 0; } .sae-config-scroll-root { @@ -25,20 +25,20 @@ .sae-config-scroll-root.sae-config-scroll-root--active::-webkit-scrollbar-thumb { background: rgb(var(--v-theme-perfect-scrollbar-thumb)); } -.sae-config-header-sentinel[data-v-a840fd50] { +.sae-config-header-sentinel[data-v-9b738b9d] { block-size: 1px; margin-block-end: -1px; pointer-events: none; } -.sae-field-section[data-v-a840fd50], -.sae-impact-preview[data-v-a840fd50] { +.sae-field-section[data-v-9b738b9d], +.sae-impact-preview[data-v-9b738b9d] { border: var(--app-surface-border); border-radius: var(--app-surface-radius); backdrop-filter: var(--app-grouped-list-backdrop-filter); background: var(--app-grouped-list-background); box-shadow: var(--app-surface-shadow); } -.sae-config-header[data-v-a840fd50] { +.sae-config-header[data-v-9b738b9d] { position: sticky; z-index: 20; inset-block-start: 0; @@ -54,7 +54,7 @@ box-shadow: none; gap: 16px; } -.sae-config-header--scrolled[data-v-a840fd50] { +.sae-config-header--scrolled[data-v-9b738b9d] { --sae-header-background: var(--app-grouped-list-background); --sae-header-backdrop-filter: var(--app-grouped-list-backdrop-filter); @@ -70,40 +70,40 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr --sae-header-background: rgba(var(--v-theme-surface), 0.92); --sae-header-backdrop-filter: none; } -.sae-config-header__brand[data-v-a840fd50] { +.sae-config-header__brand[data-v-9b738b9d] { display: flex; flex: 1 1 auto; align-items: center; min-inline-size: 0; gap: 10px; } -.sae-config-header__logo[data-v-a840fd50] { +.sae-config-header__logo[data-v-9b738b9d] { display: block; flex: 0 0 40px; block-size: 40px; inline-size: 40px; object-fit: contain; } -.sae-config-header__identity[data-v-a840fd50] { +.sae-config-header__identity[data-v-9b738b9d] { min-inline-size: 0; } -.sae-config-header__crumbs[data-v-a840fd50], -.sae-config-header__title-row[data-v-a840fd50] { +.sae-config-header__crumbs[data-v-9b738b9d], +.sae-config-header__title-row[data-v-9b738b9d] { display: flex; align-items: center; min-inline-size: 0; } -.sae-config-header__crumbs[data-v-a840fd50] { +.sae-config-header__crumbs[data-v-9b738b9d] { margin-block-end: 3px; color: rgba(var(--v-theme-on-surface), 0.55); font-size: 0.6875rem; line-height: 1rem; gap: 2px; } -.sae-config-header__title-row[data-v-a840fd50] { +.sae-config-header__title-row[data-v-9b738b9d] { gap: 8px; } -.sae-config-header__title[data-v-a840fd50] { +.sae-config-header__title[data-v-9b738b9d] { margin: 0; overflow-wrap: anywhere; font-size: 1.0625rem; @@ -111,59 +111,61 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr letter-spacing: 0; line-height: 1.4rem; } -.sae-config-header__actions[data-v-a840fd50] { +.sae-config-header__actions[data-v-9b738b9d] { display: flex; flex: 0 0 auto; align-items: center; gap: 8px; } -.sae-config-header__run[data-v-a840fd50] { +.sae-config-header__run[data-v-9b738b9d] { min-inline-size: 112px; font-weight: 600; } -.sae-config-header__save[data-v-a840fd50] { +.sae-config-header__save[data-v-9b738b9d] { min-inline-size: 120px; font-weight: 600; } -.sae-config-header__close-action[data-v-a840fd50] { +.sae-config-header__close-action[data-v-9b738b9d] { min-inline-size: 96px; color: rgba(var(--v-theme-on-surface), 0.78); font-weight: 600; - transition: background-color 180ms ease, color 180ms ease; + transition: + background-color 180ms ease, + color 180ms ease; } @media (hover: hover) and (pointer: fine) { -.sae-config-header__close-action[data-v-a840fd50]:hover { +.sae-config-header__close-action[data-v-9b738b9d]:hover { color: rgb(var(--v-theme-on-surface)); } } -.sae-config-header__close-action[data-v-a840fd50]:focus-visible { +.sae-config-header__close-action[data-v-9b738b9d]:focus-visible { color: rgb(var(--v-theme-on-surface)); outline: 2px solid rgba(var(--v-theme-primary), 0.58); outline-offset: 2px; } -.sae-config-header__close-action[data-v-a840fd50]:active { +.sae-config-header__close-action[data-v-9b738b9d]:active { background-color: rgba(var(--v-theme-on-surface), 0.04); color: rgb(var(--v-theme-on-surface)); } @media (prefers-reduced-motion: reduce) { -.sae-config-header__close-action[data-v-a840fd50], - .sae-config-header__close-action[data-v-a840fd50] .v-btn__overlay { +.sae-config-header__close-action[data-v-9b738b9d], + .sae-config-header__close-action[data-v-9b738b9d] .v-btn__overlay { transition: none; } -.sae-config-header__close-action[data-v-a840fd50] .v-ripple__container { +.sae-config-header__close-action[data-v-9b738b9d] .v-ripple__container { display: none; } } -.sae-config-header__close-icon[data-v-a840fd50] { +.sae-config-header__close-icon[data-v-9b738b9d] { flex: 0 0 40px; block-size: 40px; inline-size: 40px; } -.sae-config__body[data-v-a840fd50] { +.sae-config__body[data-v-9b738b9d] { min-inline-size: 0; padding: 12px; } -.sae-config-layout[data-v-a840fd50] { +.sae-config-layout[data-v-9b738b9d] { display: grid; min-inline-size: 0; margin-block-start: 12px; @@ -173,18 +175,18 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr 'preview'; grid-template-columns: minmax(0, 1fr); } -.sae-group-nav[data-v-a840fd50] { +.sae-group-nav[data-v-9b738b9d] { display: none; min-inline-size: 0; grid-area: navigation; } -.sae-group-nav__heading[data-v-a840fd50] { +.sae-group-nav__heading[data-v-9b738b9d] { padding: 6px 10px 10px; color: rgba(var(--v-theme-on-surface), 0.54); font-size: 0.75rem; font-weight: 600; } -.sae-group-nav > .sae-group-nav__list.v-list[data-v-a840fd50] { +.sae-group-nav > .sae-group-nav__list.v-list[data-v-9b738b9d] { flex: 1 1 auto; min-block-size: 0; overflow-y: auto; @@ -193,27 +195,27 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr background: transparent; background-color: transparent; } -.sae-group-nav__list[data-v-a840fd50] .v-list-item { +.sae-group-nav__list[data-v-9b738b9d] .v-list-item { position: relative; min-block-size: 50px; padding-inline: 12px; margin-block: 4px; } -.sae-group-nav__list[data-v-a840fd50] .v-list-item-title { +.sae-group-nav__list[data-v-9b738b9d] .v-list-item-title { overflow-wrap: anywhere; font-size: 0.875rem; font-weight: 600; letter-spacing: 0; line-height: 1.2rem; } -.sae-group-nav__list[data-v-a840fd50] .v-list-item__prepend > .v-icon { +.sae-group-nav__list[data-v-9b738b9d] .v-list-item__prepend > .v-icon { font-size: 1.25rem; } -.sae-group-nav__list[data-v-a840fd50] .v-list-item--active { +.sae-group-nav__list[data-v-9b738b9d] .v-list-item--active { background: rgba(var(--v-theme-primary), 0.09); color: rgb(var(--v-theme-primary)); } -.sae-group-nav__list[data-v-a840fd50] .v-list-item--active::before { +.sae-group-nav__list[data-v-9b738b9d] .v-list-item--active::before { position: absolute; inset-block: 8px; inset-inline-start: 0; @@ -222,7 +224,7 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr background: rgb(var(--v-theme-primary)); content: ''; } -.sae-group-nav__help[data-v-a840fd50] { +.sae-group-nav__help[data-v-9b738b9d] { flex: 0 0 auto; padding: 12px; margin-block-start: 10px; @@ -230,29 +232,29 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr border-radius: var(--app-control-radius); background: rgba(var(--v-theme-on-surface), 0.025); } -.sae-group-nav__help-title[data-v-a840fd50] { +.sae-group-nav__help-title[data-v-9b738b9d] { display: block; font-size: 0.8125rem; line-height: 1.1rem; } -.sae-group-nav__help p[data-v-a840fd50] { +.sae-group-nav__help p[data-v-9b738b9d] { margin: 6px 0 0; color: rgba(var(--v-theme-on-surface), 0.56); font-size: 0.6875rem; line-height: 1rem; } -.sae-group-nav__help-link[data-v-a840fd50] { +.sae-group-nav__help-link[data-v-9b738b9d] { min-inline-size: 0; min-block-size: 28px; padding-inline: 0; margin-block-start: 7px; font-size: 0.75rem; } -.sae-field-surface[data-v-a840fd50] { +.sae-field-surface[data-v-9b738b9d] { min-inline-size: 0; grid-area: content; } -.sae-field-surface__heading[data-v-a840fd50] { +.sae-field-surface__heading[data-v-9b738b9d] { display: flex; align-items: flex-start; justify-content: space-between; @@ -260,33 +262,33 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr padding: 2px 2px 12px; gap: 12px; } -.sae-field-surface__heading-copy[data-v-a840fd50] { +.sae-field-surface__heading-copy[data-v-9b738b9d] { display: flex; align-items: flex-start; min-inline-size: 0; gap: 9px; } -.sae-field-surface__mobile-actions[data-v-a840fd50] { +.sae-field-surface__mobile-actions[data-v-9b738b9d] { display: flex; flex: 0 0 auto; align-items: center; gap: 4px; } -.sae-mobile-group-action[data-v-a840fd50], -.sae-mobile-help[data-v-a840fd50] { +.sae-mobile-group-action[data-v-9b738b9d], +.sae-mobile-help[data-v-9b738b9d] { block-size: 36px; inline-size: 36px; } -.sae-field-surface h2[data-v-a840fd50], -.sae-impact-preview h2[data-v-a840fd50] { +.sae-field-surface h2[data-v-9b738b9d], +.sae-impact-preview h2[data-v-9b738b9d] { margin: 0; font-size: 1rem; font-weight: 700; letter-spacing: 0; line-height: 1.25rem; } -.sae-field-surface__heading p[data-v-a840fd50], -.sae-impact-preview p[data-v-a840fd50] { +.sae-field-surface__heading p[data-v-9b738b9d], +.sae-impact-preview p[data-v-9b738b9d] { margin: 3px 0 0; overflow-wrap: anywhere; color: rgba(var(--v-theme-on-surface), 0.62); @@ -294,14 +296,14 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr letter-spacing: 0; line-height: 1.05rem; } -.sae-field-section[data-v-a840fd50] { +.sae-field-section[data-v-9b738b9d] { overflow: hidden; min-inline-size: 0; } -.sae-field-section + .sae-field-section[data-v-a840fd50] { +.sae-field-section + .sae-field-section[data-v-9b738b9d] { margin-block-start: 12px; } -.sae-field-section > h3[data-v-a840fd50] { +.sae-field-section > h3[data-v-9b738b9d] { padding: 14px 16px 10px; margin: 0; font-size: 0.9375rem; @@ -309,10 +311,10 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr letter-spacing: 0; line-height: 1.25rem; } -.sae-field-section__rows[data-v-a840fd50] { +.sae-field-section__rows[data-v-9b738b9d] { padding-inline: 16px; } -.sae-field-row[data-v-a840fd50] { +.sae-field-row[data-v-9b738b9d] { display: grid; align-items: start; min-inline-size: 0; @@ -321,13 +323,13 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr gap: 18px; grid-template-columns: minmax(200px, 1.45fr) minmax(180px, 0.75fr); } -.sae-field-row--switch[data-v-a840fd50] { +.sae-field-row--switch[data-v-9b738b9d] { align-items: center; } -.sae-field-row__copy[data-v-a840fd50] { +.sae-field-row__copy[data-v-9b738b9d] { min-inline-size: 0; } -.sae-field-row__label[data-v-a840fd50] { +.sae-field-row__label[data-v-9b738b9d] { display: flex; align-items: center; color: rgb(var(--v-theme-on-surface)); @@ -336,37 +338,37 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr line-height: 1.15rem; gap: 6px; } -.sae-field-row__copy p[data-v-a840fd50] { +.sae-field-row__copy p[data-v-9b738b9d] { margin: 4px 0 0; color: rgba(var(--v-theme-on-surface), 0.57); font-size: 0.6875rem; line-height: 1rem; } -.sae-field-control[data-v-a840fd50], -.sae-field-control[data-v-a840fd50] .v-input { +.sae-field-control[data-v-9b738b9d], +.sae-field-control[data-v-9b738b9d] .v-input { min-inline-size: 0; max-inline-size: 100%; } -.sae-field-control[data-v-a840fd50] .v-select__selection { +.sae-field-control[data-v-9b738b9d] .v-select__selection { justify-content: flex-end; margin-inline-start: auto; text-align: end; } -.sae-text-control[data-v-a840fd50] input { +.sae-text-control[data-v-9b738b9d] input { text-align: end; } -.sae-select-summary__primary[data-v-a840fd50] { +.sae-select-summary__primary[data-v-9b738b9d] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.sae-select-summary__count[data-v-a840fd50] { +.sae-select-summary__count[data-v-9b738b9d] { flex: 0 0 auto; color: rgba(var(--v-theme-on-surface), 0.58); margin-inline-start: 6px; white-space: nowrap; } -.sae-number-stepper[data-v-a840fd50] { +.sae-number-stepper[data-v-9b738b9d] { display: grid; align-items: center; min-block-size: 40px; @@ -376,20 +378,20 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr border-radius: var(--app-control-radius); grid-template-columns: 40px minmax(54px, 1fr) 40px auto; } -.sae-number-stepper[data-v-a840fd50] .v-btn { +.sae-number-stepper[data-v-9b738b9d] .v-btn { min-inline-size: 40px; block-size: 40px; border-radius: 0; } -.sae-number-stepper[data-v-a840fd50] .v-field__input { +.sae-number-stepper[data-v-9b738b9d] .v-field__input { min-block-size: 40px; padding: 0 6px; text-align: center; } -.sae-number-stepper[data-v-a840fd50] input { +.sae-number-stepper[data-v-9b738b9d] input { text-align: center; } -.sae-number-stepper__unit[data-v-a840fd50] { +.sae-number-stepper__unit[data-v-9b738b9d] { min-inline-size: 38px; padding-inline: 8px; border-inline-start: 1px solid rgba(var(--v-theme-on-surface), 0.12); @@ -398,11 +400,11 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr text-align: center; white-space: nowrap; } -.sae-field-row--switch .sae-field-control[data-v-a840fd50] { +.sae-field-row--switch .sae-field-control[data-v-9b738b9d] { display: flex; justify-content: flex-end; } -.sae-tracker-entry[data-v-a840fd50] { +.sae-tracker-entry[data-v-9b738b9d] { display: flex; align-items: center; justify-content: space-between; @@ -410,23 +412,23 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr padding: 16px; gap: 12px; } -.sae-tracker-entry__copy[data-v-a840fd50] { +.sae-tracker-entry__copy[data-v-9b738b9d] { display: flex; align-items: flex-start; min-inline-size: 0; gap: 9px; } -.sae-tracker-entry__copy > div[data-v-a840fd50] { +.sae-tracker-entry__copy > div[data-v-9b738b9d] { min-inline-size: 0; } -.sae-tracker-entry strong[data-v-a840fd50] { +.sae-tracker-entry strong[data-v-9b738b9d] { display: block; overflow-wrap: anywhere; font-size: 0.875rem; letter-spacing: 0; line-height: 1.2rem; } -.sae-tracker-entry p[data-v-a840fd50] { +.sae-tracker-entry p[data-v-9b738b9d] { margin: 3px 0 0; overflow-wrap: anywhere; color: rgba(var(--v-theme-on-surface), 0.62); @@ -434,52 +436,52 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr letter-spacing: 0; line-height: 1.05rem; } -.sae-tracker-entry[data-v-a840fd50] .v-btn { +.sae-tracker-entry[data-v-9b738b9d] .v-btn { flex: 0 1 auto; min-inline-size: 0; block-size: auto; min-block-size: 36px; padding-block: 7px; } -.sae-tracker-entry[data-v-a840fd50] .v-btn__content { +.sae-tracker-entry[data-v-9b738b9d] .v-btn__content { white-space: normal; overflow-wrap: anywhere; } -.sae-impact-preview[data-v-a840fd50] { +.sae-impact-preview[data-v-9b738b9d] { min-inline-size: 0; align-self: start; padding: 16px; grid-area: preview; } -.sae-impact-preview__title[data-v-a840fd50] { +.sae-impact-preview__title[data-v-9b738b9d] { display: flex; align-items: center; min-inline-size: 0; gap: 8px; } -.sae-impact-preview strong[data-v-a840fd50] { +.sae-impact-preview strong[data-v-9b738b9d] { display: block; overflow-wrap: anywhere; font-size: 0.875rem; letter-spacing: 0; line-height: 1.25rem; } -.sae-impact-preview__list[data-v-a840fd50] { +.sae-impact-preview__list[data-v-9b738b9d] { padding: 0; margin: 10px 0 0; list-style: none; } -.sae-impact-preview__item[data-v-a840fd50], -.sae-runtime-summary__row[data-v-a840fd50], -.sae-runtime-summary__state[data-v-a840fd50], -.sae-runtime-summary__title[data-v-a840fd50] { +.sae-impact-preview__item[data-v-9b738b9d], +.sae-runtime-summary__row[data-v-9b738b9d], +.sae-runtime-summary__state[data-v-9b738b9d], +.sae-runtime-summary__title[data-v-9b738b9d] { display: grid; align-items: start; min-inline-size: 0; gap: 10px; grid-template-columns: 28px minmax(0, 1fr); } -.sae-impact-preview__item[data-v-a840fd50] { +.sae-impact-preview__item[data-v-9b738b9d] { align-items: center; padding-block: 10px; color: rgba(var(--v-theme-on-surface), 0.72); @@ -487,49 +489,49 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr line-height: 1.25rem; grid-template-columns: 28px minmax(0, 1fr) minmax(0, auto); } -.sae-impact-preview__item > .v-icon[data-v-a840fd50], -.sae-runtime-summary__row > .v-icon[data-v-a840fd50] { +.sae-impact-preview__item > .v-icon[data-v-9b738b9d], +.sae-runtime-summary__row > .v-icon[data-v-9b738b9d] { justify-self: center; color: rgba(var(--v-theme-on-surface), 0.54); } -.sae-impact-preview__item > span[data-v-a840fd50], -.sae-impact-preview__item > strong[data-v-a840fd50] { +.sae-impact-preview__item > span[data-v-9b738b9d], +.sae-impact-preview__item > strong[data-v-9b738b9d] { min-inline-size: 0; overflow-wrap: anywhere; } -.sae-impact-preview__item > strong[data-v-a840fd50] { +.sae-impact-preview__item > strong[data-v-9b738b9d] { text-align: end; } -.sae-runtime-summary[data-v-a840fd50], -.sae-change-summary[data-v-a840fd50] { +.sae-runtime-summary[data-v-9b738b9d], +.sae-change-summary[data-v-9b738b9d] { padding-block-start: 16px; margin-block-start: 16px; border-block-start: 1px solid rgba(var(--v-theme-on-surface), 0.1); } -.sae-summary-section__title[data-v-a840fd50] { +.sae-summary-section__title[data-v-9b738b9d] { display: grid; align-items: center; gap: 10px; grid-template-columns: 28px minmax(0, 1fr); } -.sae-summary-section__title > .v-icon[data-v-a840fd50] { +.sae-summary-section__title > .v-icon[data-v-9b738b9d] { block-size: 28px; inline-size: 28px; border-radius: var(--app-control-radius); background: rgba(var(--v-theme-primary), 0.1); } -.sae-change-summary .sae-summary-section__title > .v-icon[data-v-a840fd50] { +.sae-change-summary .sae-summary-section__title > .v-icon[data-v-9b738b9d] { background: rgba(var(--v-theme-warning), 0.12); } -.sae-summary-section__title h3[data-v-a840fd50] { +.sae-summary-section__title h3[data-v-9b738b9d] { margin: 0; font-size: 1rem; font-weight: 600; letter-spacing: 0; line-height: 1.25rem; } -.sae-runtime-summary__state[data-v-a840fd50], -.sae-runtime-summary__row[data-v-a840fd50] { +.sae-runtime-summary__state[data-v-9b738b9d], +.sae-runtime-summary__row[data-v-9b738b9d] { align-items: center; padding-block: 7px; color: rgba(var(--v-theme-on-surface), 0.7); @@ -537,34 +539,34 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr letter-spacing: 0; line-height: 1.25rem; } -.sae-runtime-summary__state[data-v-a840fd50] { +.sae-runtime-summary__state[data-v-9b738b9d] { margin-block-start: 6px; } -.sae-runtime-summary__metrics[data-v-a840fd50] { +.sae-runtime-summary__metrics[data-v-9b738b9d] { margin-block-start: 10px; } -.sae-runtime-summary__row[data-v-a840fd50] { +.sae-runtime-summary__row[data-v-9b738b9d] { grid-template-columns: 28px minmax(0, 1fr) minmax(0, auto); } -.sae-runtime-summary__row span[data-v-a840fd50], -.sae-runtime-summary__row strong[data-v-a840fd50] { +.sae-runtime-summary__row span[data-v-9b738b9d], +.sae-runtime-summary__row strong[data-v-9b738b9d] { min-inline-size: 0; overflow-wrap: anywhere; } -.sae-runtime-summary__row strong[data-v-a840fd50] { +.sae-runtime-summary__row strong[data-v-9b738b9d] { color: rgb(var(--v-theme-on-surface)); font-weight: 600; text-align: end; } -.sae-runtime-summary__unavailable[data-v-a840fd50] { +.sae-runtime-summary__unavailable[data-v-9b738b9d] { margin-block-start: 9px; } -.sae-change-summary ul[data-v-a840fd50] { +.sae-change-summary ul[data-v-9b738b9d] { padding: 0; margin: 8px 0 0; list-style: none; } -.sae-change-summary li[data-v-a840fd50] { +.sae-change-summary li[data-v-9b738b9d] { display: grid; align-items: center; min-inline-size: 0; @@ -575,31 +577,31 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr gap: 8px; grid-template-columns: 12px minmax(0, 1fr); } -.sae-change-summary li span[data-v-a840fd50] { +.sae-change-summary li span[data-v-9b738b9d] { min-inline-size: 0; overflow-wrap: anywhere; } -.sae-change-summary > p[data-v-a840fd50] { +.sae-change-summary > p[data-v-9b738b9d] { margin: 4px 0 0 20px; color: rgb(var(--v-theme-warning)); font-size: 0.75rem; } -.sae-tracker-dialog__title[data-v-a840fd50] { +.sae-tracker-dialog__title[data-v-9b738b9d] { display: flex; align-items: center; justify-content: space-between; min-inline-size: 0; gap: 12px; } -.sae-tracker-dialog__title > span[data-v-a840fd50] { +.sae-tracker-dialog__title > span[data-v-9b738b9d] { min-inline-size: 0; overflow-wrap: anywhere; white-space: normal; } -.sae-tracker-dialog__actions[data-v-a840fd50] { +.sae-tracker-dialog__actions[data-v-9b738b9d] { flex-wrap: wrap; } -.sae-mobile-save-dock[data-v-a840fd50] { +.sae-mobile-save-dock[data-v-9b738b9d] { position: sticky; z-index: 20; inset-block-end: calc(12px + env(safe-area-inset-bottom)); @@ -617,7 +619,7 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-config-header--scr box-shadow: 0 -8px 24px rgba(var(--v-theme-on-surface), 0.06); gap: 12px; } -.sae-mobile-save-dock__save[data-v-a840fd50] { +.sae-mobile-save-dock__save[data-v-9b738b9d] { min-inline-size: 128px; font-weight: 600; } @@ -629,142 +631,142 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-mobile-save-dock { background: rgba(var(--v-theme-surface), 0.98); backdrop-filter: none; } -.sae-yaml-dialog__content[data-v-a840fd50] { +.sae-yaml-dialog__content[data-v-9b738b9d] { min-block-size: min(60dvh, 560px); padding: 0 !important; } -.sae-yaml-editor[data-v-a840fd50] { +.sae-yaml-editor[data-v-9b738b9d] { block-size: min(60dvh, 560px); inline-size: 100%; } -.sae-mobile-group-sheet[data-v-a840fd50] .v-bottom-sheet__content { +.sae-mobile-group-sheet[data-v-9b738b9d] .v-bottom-sheet__content { max-block-size: min(82dvh, 680px); } @container (width <= 480px) { -.sae-config-header[data-v-a840fd50] { +.sae-config-header[data-v-9b738b9d] { min-block-size: 64px; padding-inline: 10px; gap: 8px; } -.sae-config-header__logo[data-v-a840fd50] { +.sae-config-header__logo[data-v-9b738b9d] { flex-basis: 34px; block-size: 34px; inline-size: 34px; } -.sae-config-header__crumbs[data-v-a840fd50] { +.sae-config-header__crumbs[data-v-9b738b9d] { display: none; } -.sae-config-header__title[data-v-a840fd50] { +.sae-config-header__title[data-v-9b738b9d] { font-size: 0.875rem; line-height: 1.1rem; } -.sae-config-header__title-row[data-v-a840fd50] { +.sae-config-header__title-row[data-v-9b738b9d] { gap: 5px; } -.sae-tracker-entry[data-v-a840fd50] { +.sae-tracker-entry[data-v-9b738b9d] { align-items: stretch; flex-direction: column; } -.sae-tracker-entry[data-v-a840fd50] .v-btn { +.sae-tracker-entry[data-v-9b738b9d] .v-btn { inline-size: 100%; } } @container (width < 720px) { -.sae-config-header__run[data-v-a840fd50], - .sae-config-header__save[data-v-a840fd50], - .sae-config-header__close-action[data-v-a840fd50], - .sae-change-summary[data-v-a840fd50] { +.sae-config-header__run[data-v-9b738b9d], + .sae-config-header__save[data-v-9b738b9d], + .sae-config-header__close-action[data-v-9b738b9d], + .sae-change-summary[data-v-9b738b9d] { display: none; } -.sae-field-row[data-v-a840fd50] { +.sae-field-row[data-v-9b738b9d] { align-items: center; gap: 12px; grid-template-columns: minmax(0, 1fr) minmax(180px, 0.9fr); } -.sae-field-row--switch[data-v-a840fd50] { +.sae-field-row--switch[data-v-9b738b9d] { grid-template-columns: minmax(0, 1fr) auto; } -.sae-field-row--switch .sae-field-control[data-v-a840fd50] { +.sae-field-row--switch .sae-field-control[data-v-9b738b9d] { align-self: center; } } @container (width <= 30rem) { -.sae-field-row[data-v-a840fd50] { +.sae-field-row[data-v-9b738b9d] { align-items: center; padding-block: 0.625rem; column-gap: 0.75rem; row-gap: 0; grid-template-columns: minmax(0, 1fr) minmax(8.5rem, 10rem); } -.sae-field-row__copy[data-v-a840fd50] { +.sae-field-row__copy[data-v-9b738b9d] { display: contents; } -.sae-field-row__label[data-v-a840fd50] { +.sae-field-row__label[data-v-9b738b9d] { grid-column: 1; grid-row: 1; } -.sae-field-row__copy p[data-v-a840fd50] { +.sae-field-row__copy p[data-v-9b738b9d] { grid-column: 1 / -1; grid-row: 2; margin-block-start: 1px; line-height: 0.9375rem; } -.sae-field-control[data-v-a840fd50] { +.sae-field-control[data-v-9b738b9d] { grid-column: 2; grid-row: 1; justify-self: end; inline-size: 100%; } -.sae-field-row--switch .sae-field-control[data-v-a840fd50] { +.sae-field-row--switch .sae-field-control[data-v-9b738b9d] { justify-self: end; inline-size: auto; } -.sae-field-control[data-v-a840fd50] .v-field { +.sae-field-control[data-v-9b738b9d] .v-field { min-block-size: 1.75rem; border-radius: 0; background: transparent; box-shadow: none; } -.sae-field-control[data-v-a840fd50] .v-field__outline { +.sae-field-control[data-v-9b738b9d] .v-field__outline { display: none; } -.sae-field-control[data-v-a840fd50] .v-field__input { +.sae-field-control[data-v-9b738b9d] .v-field__input { min-block-size: 1.75rem; padding-block: 0; padding-inline: 0; } -.sae-field-control[data-v-a840fd50] .v-field__append-inner { +.sae-field-control[data-v-9b738b9d] .v-field__append-inner { padding-block-start: 0.25rem; padding-inline-start: 0.25rem; } -.sae-field-control[data-v-a840fd50] .v-select__selection { +.sae-field-control[data-v-9b738b9d] .v-select__selection { justify-content: flex-end; } -.sae-number-stepper[data-v-a840fd50] { +.sae-number-stepper[data-v-9b738b9d] { min-block-size: 1.75rem; border: 0; border-radius: 0; grid-template-columns: minmax(2.625rem, 1fr) auto; } -.sae-number-stepper[data-v-a840fd50] .v-btn { +.sae-number-stepper[data-v-9b738b9d] .v-btn { display: none; } -.sae-number-stepper[data-v-a840fd50] .v-field__input, - .sae-number-stepper[data-v-a840fd50] input { +.sae-number-stepper[data-v-9b738b9d] .v-field__input, + .sae-number-stepper[data-v-9b738b9d] input { min-block-size: 1.75rem; text-align: end; } -.sae-number-stepper__unit[data-v-a840fd50] { +.sae-number-stepper__unit[data-v-9b738b9d] { min-inline-size: auto; padding-inline-end: 0; border-inline-start: 0; } } @container (width >= 720px) { -.sae-config__body[data-v-a840fd50] { +.sae-config__body[data-v-9b738b9d] { padding: 14px; } -.sae-config-layout[data-v-a840fd50] { +.sae-config-layout[data-v-9b738b9d] { padding-block-end: 14px; gap: 14px; grid-template-areas: @@ -772,13 +774,13 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-mobile-save-dock { 'navigation preview'; grid-template-columns: 168px minmax(0, 1fr); } -.sae-config-header__close-icon[data-v-a840fd50], - .sae-field-row--mobile-only[data-v-a840fd50], - .sae-field-surface__mobile-actions[data-v-a840fd50], - .sae-mobile-save-dock[data-v-a840fd50] { +.sae-config-header__close-icon[data-v-9b738b9d], + .sae-field-row--mobile-only[data-v-9b738b9d], + .sae-field-surface__mobile-actions[data-v-9b738b9d], + .sae-mobile-save-dock[data-v-9b738b9d] { display: none; } -.sae-group-nav[data-v-a840fd50] { +.sae-group-nav[data-v-9b738b9d] { position: sticky; inset-block-start: 86px; display: flex; @@ -789,47 +791,47 @@ html[data-theme='transparent'].transparent-blur-disabled .sae-mobile-save-dock { border-inline-end: 1px solid rgba(var(--v-theme-on-surface), 0.1); padding-inline-end: 10px; } -.sae-impact-preview[data-v-a840fd50] { +.sae-impact-preview[data-v-9b738b9d] { align-self: start; } } @container (width >= 880px) { -.sae-config__form[data-v-a840fd50] { +.sae-config__form[data-v-9b738b9d] { display: grid; overflow: hidden; block-size: min(90dvh, 820px); grid-template-rows: 1px auto minmax(0, 1fr); } -.sae-config__body[data-v-a840fd50], - .sae-config-layout[data-v-a840fd50] { +.sae-config__body[data-v-9b738b9d], + .sae-config-layout[data-v-9b738b9d] { min-block-size: 0; block-size: 100%; } -.sae-config__body[data-v-a840fd50] { +.sae-config__body[data-v-9b738b9d] { overflow: hidden; } -.sae-config-layout[data-v-a840fd50] { +.sae-config-layout[data-v-9b738b9d] { align-items: stretch; grid-template-areas: 'navigation content preview'; grid-template-columns: 168px minmax(0, 1fr) 232px; } -.sae-group-nav[data-v-a840fd50] { +.sae-group-nav[data-v-9b738b9d] { position: static; overflow: hidden; block-size: 100%; } -.sae-impact-preview[data-v-a840fd50] { +.sae-impact-preview[data-v-9b738b9d] { position: static; overflow: hidden; block-size: 100%; } -.sae-field-surface[data-v-a840fd50] { +.sae-field-surface[data-v-9b738b9d] { overflow-x: hidden; overflow-y: auto; min-block-size: 0; padding-inline-end: 4px; } -.sae-impact-preview[data-v-a840fd50] { +.sae-impact-preview[data-v-9b738b9d] { align-self: stretch; } } diff --git a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/index-BcyFOw8O.js b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/index-C5Qd1Rsy.js similarity index 82% rename from plugins.v2/subscribeassistantenhanced/frontend/dist/assets/index-BcyFOw8O.js rename to plugins.v2/subscribeassistantenhanced/frontend/dist/assets/index-C5Qd1Rsy.js index b9eb0608..50798b42 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/index-BcyFOw8O.js +++ b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/index-C5Qd1Rsy.js @@ -1 +1 @@ -export { default as Config } from './__federation_expose_Config-Cp4ctAFt.js'; +export { default as Config } from './__federation_expose_Config-C8q1BGHX.js'; diff --git a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/remoteEntry.js b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/remoteEntry.js index bea8249a..8207d257 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/remoteEntry.js +++ b/plugins.v2/subscribeassistantenhanced/frontend/dist/assets/remoteEntry.js @@ -2,8 +2,8 @@ const currentImports = {}; const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); let moduleMap = { "./Config":()=>{ - dynamicLoadingCss(["__federation_expose_Config-DByuQ3DF.css"], false, './Config'); - return __federation_import('./__federation_expose_Config-Cp4ctAFt.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},}; + dynamicLoadingCss(["__federation_expose_Config-CnHfUIf2.css"], false, './Config'); + return __federation_import('./__federation_expose_Config-C8q1BGHX.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},}; const seen = {}; const dynamicLoadingCss = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { const metaUrl = import.meta.url; diff --git a/plugins.v2/subscribeassistantenhanced/frontend/eslint.config.js b/plugins.v2/subscribeassistantenhanced/frontend/eslint.config.js new file mode 100644 index 00000000..545a3966 --- /dev/null +++ b/plugins.v2/subscribeassistantenhanced/frontend/eslint.config.js @@ -0,0 +1,111 @@ +import js from '@eslint/js' +import sonarjs from 'eslint-plugin-sonarjs' +import pluginVue from 'eslint-plugin-vue' +import globals from 'globals' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +const managedFiles = ['**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}', '**/*.vue'] +const browserFiles = [ + 'plugins.v2/subscribeassistantenhanced/frontend/src/**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts,vue}', + 'tests/v2/subscribeassistantenhanced/frontend/**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts,vue}', +] +const nodeFiles = ['plugins.v2/subscribeassistantenhanced/frontend/*.config.{js,mjs,cjs,ts,mts,cts}'] + +const browserGlobalNames = new Set(Object.keys(globals.browser)) +const nodeOnlyGlobalRestrictions = Object.keys(globals.node) + .filter(name => !browserGlobalNames.has(name)) + .sort() + .map(name => ({ + name, + message: `'${name}' is only available in Node.js code.`, + })) + +const sonarRules = { + 'sonarjs/array-callback-without-return': 'error', + 'sonarjs/code-eval': 'error', + 'sonarjs/empty-string-repetition': 'error', + 'sonarjs/no-all-duplicated-branches': 'error', + 'sonarjs/no-dead-store': 'error', + 'sonarjs/no-duplicated-branches': 'error', + 'sonarjs/no-element-overwrite': 'error', + 'sonarjs/no-hardcoded-passwords': 'error', + 'sonarjs/no-hardcoded-secrets': 'error', + 'sonarjs/no-identical-conditions': 'error', + 'sonarjs/no-identical-expressions': 'error', + 'sonarjs/no-ignored-exceptions': 'error', + 'sonarjs/no-unthrown-error': 'error', + 'sonarjs/no-use-of-empty-return-value': 'error', + 'sonarjs/reduce-initial-value': 'error', + 'sonarjs/slow-regex': 'error', + 'sonarjs/stateful-regex': 'error', + 'sonarjs/super-linear-regex': 'error', +} + +const typescriptConfigs = tseslint.configs.recommended.map(config => ({ + ...config, + files: ['**/*.{ts,tsx,mts,cts}', '**/*.vue'], +})) + +const vueConfigs = pluginVue.configs['flat/essential'].map(config => ({ + ...config, + files: ['**/*.vue'], +})) + +export default defineConfig([ + globalIgnores(['**/node_modules/**', '**/dist/**', '**/coverage/**', '**/.worktrees/**', '**/*.d.ts']), + { + ...js.configs.recommended, + files: managedFiles, + }, + ...typescriptConfigs, + ...vueConfigs, + { + files: ['**/*.vue'], + rules: { + 'vue/multi-word-component-names': 'off', + 'vue/valid-v-slot': ['error', { allowModifiers: true }], + }, + }, + { + files: managedFiles, + languageOptions: { + ecmaVersion: 'latest', + parserOptions: { + parser: tseslint.parser, + }, + sourceType: 'module', + }, + }, + { + files: browserFiles, + languageOptions: { + globals: globals.browser, + }, + rules: { + 'no-restricted-globals': ['error', ...nodeOnlyGlobalRestrictions], + }, + }, + { + files: nodeFiles, + languageOptions: { + globals: globals.node, + }, + rules: { + 'no-restricted-globals': 'off', + }, + }, + { + files: managedFiles, + plugins: { + sonarjs, + }, + rules: sonarRules, + }, + { + files: managedFiles, + rules: { + 'no-debugger': 'error', + }, + }, +]) diff --git a/plugins.v2/subscribeassistantenhanced/frontend/package.json b/plugins.v2/subscribeassistantenhanced/frontend/package.json index 99e5b8e0..bc9970c8 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/package.json +++ b/plugins.v2/subscribeassistantenhanced/frontend/package.json @@ -6,6 +6,10 @@ "scripts": { "build": "vite build", "dev": "vite build --watch", + "format": "prettier --config .prettierrc.json --ignore-path .prettierignore . ../../../tests/v2/subscribeassistantenhanced/frontend --write", + "format:check": "prettier --config .prettierrc.json --ignore-path .prettierignore . ../../../tests/v2/subscribeassistantenhanced/frontend --check", + "lint": "cd ../../.. && eslint --config plugins.v2/subscribeassistantenhanced/frontend/eslint.config.js plugins.v2/subscribeassistantenhanced/frontend/eslint.config.js plugins.v2/subscribeassistantenhanced/frontend/src plugins.v2/subscribeassistantenhanced/frontend/vite.config.ts tests/v2/subscribeassistantenhanced/frontend --max-warnings=0", + "lint:fix": "cd ../../.. && eslint --config plugins.v2/subscribeassistantenhanced/frontend/eslint.config.js plugins.v2/subscribeassistantenhanced/frontend/eslint.config.js plugins.v2/subscribeassistantenhanced/frontend/src plugins.v2/subscribeassistantenhanced/frontend/vite.config.ts tests/v2/subscribeassistantenhanced/frontend --fix", "test": "vitest", "test:run": "vitest run", "test:coverage": "vitest run --coverage", @@ -16,6 +20,7 @@ "vuetify": "3.7.3" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@originjs/vite-plugin-federation": "^1.4.1", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.9.1", @@ -26,11 +31,22 @@ "@vue/compiler-dom": "3.5.39", "@vue/compiler-sfc": "^3.5.13", "@vue/test-utils": "2.4.11", + "eslint": "^10.7.0", + "eslint-plugin-sonarjs": "^4.2.0", + "eslint-plugin-vue": "^10.9.2", + "globals": "^17.3.0", "jsdom": "26.1.0", "msw": "2.15.0", + "prettier": "^3.9.5", "typescript": "^5.0.4", + "typescript-eslint": "^8.64.0", "vite": "^5.4.11", "vitest": "3.2.7", + "vue-eslint-parser": "^10.4.1", "vue-tsc": "^2.0.10" + }, + "packageManager": "yarn@1.22.18", + "engines": { + "node": ">=20.19" } } diff --git a/plugins.v2/subscribeassistantenhanced/frontend/src/components/Config.vue b/plugins.v2/subscribeassistantenhanced/frontend/src/components/Config.vue index 4bfc4415..75627d21 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/src/components/Config.vue +++ b/plugins.v2/subscribeassistantenhanced/frontend/src/components/Config.vue @@ -36,21 +36,22 @@ const instance = getCurrentInstance() const locale = computed(() => normalizeLocale(instance?.appContext.config.globalProperties.$i18n?.locale)) const localizedGroups = computed(() => localizeGroups(locale.value, groups)) const localizedFields = computed(() => localizeFields(locale.value, fields)) -const fieldsByKey = computed(() => new Map( - localizedFields.value - .filter(field => !field.legacyUiKey && !field.dialogOnly) - .map(field => [field.key, field]), -)) -const trackerField = computed(() => localizedFields.value.find( - field => field.key === 'default_tracker_response' && field.dialogOnly, -)!) -const yamlField = computed(() => localizedFields.value.find( - field => field.key === 'recognition_guard_custom_config', -)!) -const changedItems = computed(() => changedKeys.value - .slice(0, 3) - .map(key => localizedFields.value.find(field => field.key === key)) - .filter((field): field is FieldMeta => Boolean(field))) +const fieldsByKey = computed( + () => + new Map( + localizedFields.value.filter(field => !field.legacyUiKey && !field.dialogOnly).map(field => [field.key, field]), + ), +) +const trackerField = computed(() => + localizedFields.value.find(field => field.key === 'default_tracker_response' && field.dialogOnly)!, +) +const yamlField = computed(() => localizedFields.value.find(field => field.key === 'recognition_guard_custom_config')!) +const changedItems = computed(() => + changedKeys.value + .slice(0, 3) + .map(key => localizedFields.value.find(field => field.key === key)) + .filter((field): field is FieldMeta => Boolean(field)), +) const hiddenChangedCount = computed(() => Math.max(0, changedKeys.value.length - changedItems.value.length)) const activeGroup = ref('global') const runtimeSummary = ref(null) @@ -63,7 +64,7 @@ const configHeaderSentinel = ref(null) const fieldSurfaceHeading = ref(null) const headerScrolled = ref(false) const theme = useTheme() -const aceTheme = computed(() => theme.current.value.dark ? 'github_dark' : 'github') +const aceTheme = computed(() => (theme.current.value.dark ? 'github_dark' : 'github')) let headerObserver: IntersectionObserver | undefined let configScrollRoot: HTMLElement | null = null let fieldScrollRoot: HTMLElement | null = null @@ -145,29 +146,17 @@ const sectionDefinitions: Record = { { titleKey: 'section.siteProbe', keys: ['site_total_probe_enabled'] }, { titleKey: 'section.pausedProbe', - keys: [ - 'paused_probe_reasons', - 'paused_probe_min_pause_days', - 'paused_probe_interval_hours', - ], + keys: ['paused_probe_reasons', 'paused_probe_min_pause_days', 'paused_probe_interval_hours'], }, ], bestVersion: [ { titleKey: 'section.bestVersionScope', - keys: [ - 'best_version_type', - 'best_version_movie_remaining_days', - 'best_version_tv_remaining_days', - ], + keys: ['best_version_type', 'best_version_movie_remaining_days', 'best_version_tv_remaining_days'], }, { titleKey: 'section.backfill', - keys: [ - 'best_version_episode_to_full', - 'best_version_backfill_enabled', - 'backfill_best_version_now', - ], + keys: ['best_version_episode_to_full', 'best_version_backfill_enabled', 'backfill_best_version_now'], }, ], guard: [ @@ -220,13 +209,15 @@ const activeGroupMeta = computed( () => localizedGroups.value.find(group => group.key === activeGroup.value) ?? localizedGroups.value[0]!, ) const activeSections = computed(() => - sectionDefinitions[activeGroup.value].map(section => ({ - ...section, - title: t(locale.value, section.titleKey), - fields: section.keys - .map(key => fieldsByKey.value.get(key)) - .filter((field): field is FieldMeta => Boolean(field) && field?.kind !== 'textarea'), - })).filter(section => section.fields.length > 0), + sectionDefinitions[activeGroup.value] + .map(section => ({ + ...section, + title: t(locale.value, section.titleKey), + fields: section.keys + .map(key => fieldsByKey.value.get(key)) + .filter((field): field is FieldMeta => Boolean(field) && field?.kind !== 'textarea'), + })) + .filter(section => section.fields.length > 0), ) const cadenceSummary = computed(() => [ { @@ -253,7 +244,8 @@ const cadenceSummary = computed(() => [ const activeDomainCount = computed(() => { const values = Object.values(runtimeSummary.value?.domains ?? {}) return { - active: values.filter(value => value === true || (typeof value === 'string' && !['off', 'no'].includes(value))).length, + active: values.filter(value => value === true || (typeof value === 'string' && !['off', 'no'].includes(value))) + .length, total: values.length, } }) @@ -374,13 +366,7 @@ function runOnce(): void {
- + {{ t(locale, 'config.runOnce') }} @@ -492,11 +478,7 @@ function runOnce(): void {
-
+

{{ sectionIndex + 1 }}. {{ section.title }}

- + {{ displayFieldLabel(field) }}

{{ field.hint }}

@@ -563,10 +540,7 @@ function runOnce(): void { -
+
- + {{ trackerField.label }} {{ t(locale, 'config.yamlTitle') }} - + @@ -829,7 +804,9 @@ function runOnce(): void { - {{ t(locale, 'config.done') }} + {{ + t(locale, 'config.done') + }} @@ -990,7 +967,9 @@ function runOnce(): void { min-inline-size: 96px; color: rgba(var(--v-theme-on-surface), 0.78); font-weight: 600; - transition: background-color 180ms ease, color 180ms ease; + transition: + background-color 180ms ease, + color 180ms ease; } @media (hover: hover) and (pointer: fine) { @@ -1648,7 +1627,6 @@ function runOnce(): void { .sae-field-row--switch .sae-field-control { align-self: center; } - } @container (width <= 30rem) { diff --git a/plugins.v2/subscribeassistantenhanced/frontend/src/config/defaults.ts b/plugins.v2/subscribeassistantenhanced/frontend/src/config/defaults.ts index 41490f7d..5318b633 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/src/config/defaults.ts +++ b/plugins.v2/subscribeassistantenhanced/frontend/src/config/defaults.ts @@ -173,7 +173,8 @@ export const configDefaults: SaeConfig = { "recognition_guard_notify_interval": 3600, "recognition_guard_tmdb_recheck_mode": "balanced_strict", "recognition_guard_cache_maxsize": 100000, - "recognition_guard_custom_config": "####### 配置说明 BEGIN #######\n# 1. 本配置只控制识别增强的策略覆盖和关键词,不控制通知、二次识别触发或缓存大小。\n# 2. 未配置或保持注释的项目均继承 recognition_guard_mode 当前模板。\n# 3. actions 的值可选:inherit / observe / soft_block / block:\n# - inherit:继承当前 recognition_guard_mode 模板,不单独覆盖。\n# - observe:只记录审计和可选通知,不移除候选,下载选择不受影响。\n# - soft_block:先从候选池移除;如果整轮候选被清空,且 empty_pool 策略允许,该候选可降级为 observe 恢复。\n# - block:从候选池移除,集合级保护也不得恢复;用于用户明确不想下载的风险。\n# 4. allow 只能抵消非 hard veto 风险;不能覆盖显式 ID 错配、明确类型/形态互串、目标范围完全不覆盖等 hard veto。\n# 5. block 是普通黑名单风险,动作由 mode 或 actions.user_block 决定;hard_block 才是一律强拦截。\n# 6. 正则使用 Python re 语法;非法正则会跳过对应条目并记录配置告警,不影响其他规则。\n# 7. keywords 下的内置证据词分组如果取消注释配置,表示替换该分组;未配置的分组继续使用内置默认。\n####### 配置说明 END #######\n\nactions:\n # 候选缺少年份。多站点用户可改为 block,少站点用户建议 inherit 或 observe。\n # missing_year: block\n\n # 候选全集范围明显大于目标窗口,例如目标缺 E08-E19,候选是全 60 集。\n # target_range_oversized: block\n\n # 命中 keywords.block 时的动作。\n # user_block: soft_block\n\n # 二次识别结果与订阅目标不一致。\n # secondary_identity_conflict: block\n\nempty_pool:\n # 整轮候选被识别增强清空时的恢复策略:recover_soft_block / never_recover。\n # policy: recover_soft_block\n\n # 即使动作是 soft_block,也不允许因整轮候选清空而恢复的原因码。\n # non_recoverable_codes:\n # - target_range_oversized\n # - missing_year\n\nkeywords:\n # 白名单:只抵消非 hard veto 风险。\n # allow:\n # - 官方合集\n\n # 普通黑名单:动作由 mode 或 actions.user_block 决定。\n # block:\n # - 低可信风险词\n\n # 强黑名单:所有启用模式下 hard veto;audit 只记录 would block。\n # hard_block:\n # - 强制错误词\n\n # 以下是内置证据词分组;如需覆盖某一组,取消注释并完整写出该组。\n # live_action:\n # - 真人版\n # - 电视剧版\n # - 实拍版\n # - 真人剧\n # animation:\n # - 动画\n # - 动漫\n # - 国漫\n # - 番剧\n # movie:\n # - 电影版\n # - 剧场版\n # - 劇場版\n # - '\\bMovie\\b'\n # tv:\n # - '\\bS\\d{1,3}(?:E\\d{1,4})?\\b'\n # - '第\\s*\\d+\\s*[集季]'\n # - '全\\s*\\d+\\s*集'\n", + "recognition_guard_custom_config": + "####### 配置说明 BEGIN #######\n# 1. 本配置只控制识别增强的策略覆盖和关键词,不控制通知、二次识别触发或缓存大小。\n# 2. 未配置或保持注释的项目均继承 recognition_guard_mode 当前模板。\n# 3. actions 的值可选:inherit / observe / soft_block / block:\n# - inherit:继承当前 recognition_guard_mode 模板,不单独覆盖。\n# - observe:只记录审计和可选通知,不移除候选,下载选择不受影响。\n# - soft_block:先从候选池移除;如果整轮候选被清空,且 empty_pool 策略允许,该候选可降级为 observe 恢复。\n# - block:从候选池移除,集合级保护也不得恢复;用于用户明确不想下载的风险。\n# 4. allow 只能抵消非 hard veto 风险;不能覆盖显式 ID 错配、明确类型/形态互串、目标范围完全不覆盖等 hard veto。\n# 5. block 是普通黑名单风险,动作由 mode 或 actions.user_block 决定;hard_block 才是一律强拦截。\n# 6. 正则使用 Python re 语法;非法正则会跳过对应条目并记录配置告警,不影响其他规则。\n# 7. keywords 下的内置证据词分组如果取消注释配置,表示替换该分组;未配置的分组继续使用内置默认。\n####### 配置说明 END #######\n\nactions:\n # 候选缺少年份。多站点用户可改为 block,少站点用户建议 inherit 或 observe。\n # missing_year: block\n\n # 候选全集范围明显大于目标窗口,例如目标缺 E08-E19,候选是全 60 集。\n # target_range_oversized: block\n\n # 命中 keywords.block 时的动作。\n # user_block: soft_block\n\n # 二次识别结果与订阅目标不一致。\n # secondary_identity_conflict: block\n\nempty_pool:\n # 整轮候选被识别增强清空时的恢复策略:recover_soft_block / never_recover。\n # policy: recover_soft_block\n\n # 即使动作是 soft_block,也不允许因整轮候选清空而恢复的原因码。\n # non_recoverable_codes:\n # - target_range_oversized\n # - missing_year\n\nkeywords:\n # 白名单:只抵消非 hard veto 风险。\n # allow:\n # - 官方合集\n\n # 普通黑名单:动作由 mode 或 actions.user_block 决定。\n # block:\n # - 低可信风险词\n\n # 强黑名单:所有启用模式下 hard veto;audit 只记录 would block。\n # hard_block:\n # - 强制错误词\n\n # 以下是内置证据词分组;如需覆盖某一组,取消注释并完整写出该组。\n # live_action:\n # - 真人版\n # - 电视剧版\n # - 实拍版\n # - 真人剧\n # animation:\n # - 动画\n # - 动漫\n # - 国漫\n # - 番剧\n # movie:\n # - 电影版\n # - 剧场版\n # - 劇場版\n # - '\\bMovie\\b'\n # tv:\n # - '\\bS\\d{1,3}(?:E\\d{1,4})?\\b'\n # - '第\\s*\\d+\\s*[集季]'\n # - '全\\s*\\d+\\s*集'\n", "pending_enhanced_enabled": true, "pending_download_enabled": true, "auto_tv_pending_days": 0, @@ -188,9 +189,7 @@ export const configDefaults: SaeConfig = { "tv_no_download_days": 180, "no_download_actions": [], "site_total_probe_enabled": false, - "paused_probe_reasons": [ - "no_download" - ], + "paused_probe_reasons": ["no_download"], "paused_probe_min_pause_days": 14, "paused_probe_interval_hours": 72, "best_version_type": "no", diff --git a/plugins.v2/subscribeassistantenhanced/frontend/src/config/fields.ts b/plugins.v2/subscribeassistantenhanced/frontend/src/config/fields.ts index adcaf551..1f93f320 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/src/config/fields.ts +++ b/plugins.v2/subscribeassistantenhanced/frontend/src/config/fields.ts @@ -1,9 +1,9 @@ -import { type ConfigKey } from './defaults' +import { type ConfigKey } from "./defaults" /** Vue 字段对应的 Vuetify 控件类别。 */ -export type FieldKind = 'switch' | 'number' | 'text' | 'select' | 'multi-select' | 'cron' | 'textarea' +export type FieldKind = "switch" | "number" | "text" | "select" | "multi-select" | "cron" | "textarea" /** 配置项对订阅生命周期和数据的影响等级。 */ -export type RiskLevel = 'none' | 'notice' | 'danger' +export type RiskLevel = "none" | "notice" | "danger" /** 下拉或多选项的展示值与持久化值。 */ export interface FieldOption { @@ -15,14 +15,7 @@ export interface FieldOption { /** 配置页业务分组键。 */ export type GroupKey = - | 'global' - | 'cleanup' - | 'pending' - | 'pause' - | 'completion' - | 'bestVersion' - | 'guard' - | 'recognition' + "global" | "cleanup" | "pending" | "pause" | "completion" | "bestVersion" | "guard" | "recognition" /** 单个配置字段的渲染与风险元数据。 */ export interface FieldMeta { @@ -63,14 +56,41 @@ export interface GroupMeta { } export const groups: GroupMeta[] = [ - { key: 'global', title: '全局运行', icon: 'mdi-tune-variant', summary: '插件开关、通知、一次性动作与公共周期' }, - { key: 'cleanup', title: '订阅清理', icon: 'mdi-delete-sweep-outline', summary: '下载监控、删种、Tracker 与整理记录清理', highRisk: true }, - { key: 'pending', title: '订阅待定', icon: 'mdi-timer-sand', summary: '下载中与剧集目标未稳定时保持待定' }, - { key: 'pause', title: '订阅暂停', icon: 'mdi-pause-circle-outline', summary: '按用户、上映播出窗口和无下载策略暂停订阅' }, - { key: 'completion', title: '订阅补全', icon: 'mdi-radar', summary: '站点集数探测与暂停订阅补搜' }, - { key: 'bestVersion', title: '订阅洗版', icon: 'mdi-auto-fix', summary: '洗版范围、时限、回填和分集转全集', highRisk: true }, - { key: 'guard', title: '完结信号', icon: 'mdi-shield-check-outline', summary: '完结守卫、站点证据、波动节奏和自动纠错' }, - { key: 'recognition', title: '识别增强', icon: 'mdi-account-search-outline', summary: '候选准入、通知、二次识别和自定义策略' }, + { key: "global", title: "全局运行", icon: "mdi-tune-variant", summary: "插件开关、通知、一次性动作与公共周期" }, + { + key: "cleanup", + title: "订阅清理", + icon: "mdi-delete-sweep-outline", + summary: "下载监控、删种、Tracker 与整理记录清理", + highRisk: true + }, + { key: "pending", title: "订阅待定", icon: "mdi-timer-sand", summary: "下载中与剧集目标未稳定时保持待定" }, + { + key: "pause", + title: "订阅暂停", + icon: "mdi-pause-circle-outline", + summary: "按用户、上映播出窗口和无下载策略暂停订阅" + }, + { key: "completion", title: "订阅补全", icon: "mdi-radar", summary: "站点集数探测与暂停订阅补搜" }, + { + key: "bestVersion", + title: "订阅洗版", + icon: "mdi-auto-fix", + summary: "洗版范围、时限、回填和分集转全集", + highRisk: true + }, + { + key: "guard", + title: "完结信号", + icon: "mdi-shield-check-outline", + summary: "完结守卫、站点证据、波动节奏和自动纠错" + }, + { + key: "recognition", + title: "识别增强", + icon: "mdi-account-search-outline", + summary: "候选准入、通知、二次识别和自定义策略" + } ] export const fields: FieldMeta[] = [ diff --git a/plugins.v2/subscribeassistantenhanced/frontend/src/config/i18n.ts b/plugins.v2/subscribeassistantenhanced/frontend/src/config/i18n.ts index c46e499c..cc7461ca 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/src/config/i18n.ts +++ b/plugins.v2/subscribeassistantenhanced/frontend/src/config/i18n.ts @@ -19,13 +19,8 @@ export function normalizeLocale(source: LocaleSource): SupportedLocale { } if (typeof current !== 'string') return 'zh-CN' const normalized = current.trim().replace('_', '-').toLowerCase() - const locale = normalized === 'zh-cn' - ? 'zh-CN' - : normalized === 'zh-tw' - ? 'zh-TW' - : normalized === 'en-us' - ? 'en-US' - : 'zh-CN' + const locale = + normalized === 'zh-cn' ? 'zh-CN' : normalized === 'zh-tw' ? 'zh-TW' : normalized === 'en-us' ? 'en-US' : 'zh-CN' return supportedLocales.has(locale) ? locale : 'zh-CN' } @@ -54,7 +49,9 @@ const messages: Record> = { 'config.selectGroup': '选择配置分组', 'config.done': '完成', 'config.edit': '编辑', - 'config.decrease': '减小{label}', 'config.increase': '增大{label}', 'config.editLabel': '编辑{label}', + 'config.decrease': '减小{label}', + 'config.increase': '增大{label}', + 'config.editLabel': '编辑{label}', 'config.yamlTitle': '自定义识别规则', 'config.runtime': '运行概况', 'config.runtimeLoading': '正在读取运行概况', @@ -65,9 +62,15 @@ const messages: Record> = { 'config.off': '关闭', 'config.cronPlaceholder': '5 位 CRON 表达式', 'config.title': '订阅助手(增强版)', - 'domain.completionGuard': '完结守卫模式', 'domain.pending': '待定增强', 'domain.pause': '暂停优化', - 'domain.bestVersion': '自动洗版', 'domain.download': '下载管理', 'domain.verify': '完成后验证', - 'domain.siteTotal': '站点集数探测', 'domain.siteCompletion': '站点完结信号', 'domain.recognition': '识别增强', + 'domain.completionGuard': '完结守卫模式', + 'domain.pending': '待定增强', + 'domain.pause': '暂停优化', + 'domain.bestVersion': '自动洗版', + 'domain.download': '下载管理', + 'domain.verify': '完成后验证', + 'domain.siteTotal': '站点集数探测', + 'domain.siteCompletion': '站点完结信号', + 'domain.recognition': '识别增强', 'section.running': '运行状态', 'section.oneTime': '一次性动作', 'section.schedule': '公共周期', @@ -108,26 +111,53 @@ const messages: Record> = { 'config.aboutPlugin': '關於外掛', 'config.aboutDescription': '多場景管理訂閱,實現訂閱全生命週期管理', 'config.viewDocs': '查看文件', - 'config.plugin': '外掛', 'config.settings': '外掛設定', 'config.selectGroup': '選擇設定分組', - 'config.done': '完成', 'config.edit': '編輯', - 'config.decrease': '減少{label}', 'config.increase': '增加{label}', 'config.editLabel': '編輯{label}', + 'config.plugin': '外掛', + 'config.settings': '外掛設定', + 'config.selectGroup': '選擇設定分組', + 'config.done': '完成', + 'config.edit': '編輯', + 'config.decrease': '減少{label}', + 'config.increase': '增加{label}', + 'config.editLabel': '編輯{label}', 'config.yamlTitle': '自訂識別規則', - 'config.runtime': '執行概況', 'config.runtimeLoading': '正在讀取執行概況', - 'config.runtimeUnavailable': '執行概況暫不可用', 'config.pendingCount': '待定訂閱', - 'config.monitoredCount': '下載任務', 'config.enabled': '啟用', 'config.off': '關閉', + 'config.runtime': '執行概況', + 'config.runtimeLoading': '正在讀取執行概況', + 'config.runtimeUnavailable': '執行概況暫不可用', + 'config.pendingCount': '待定訂閱', + 'config.monitoredCount': '下載任務', + 'config.enabled': '啟用', + 'config.off': '關閉', 'config.cronPlaceholder': '5 位 CRON 表示式', 'config.title': '訂閱助手(增強版)', - 'domain.completionGuard': '完結守衛模式', 'domain.pending': '待定增強', 'domain.pause': '暫停最佳化', - 'domain.bestVersion': '自動洗版', 'domain.download': '下載管理', 'domain.verify': '完成後驗證', - 'domain.siteTotal': '站點集數探測', 'domain.siteCompletion': '站點完結訊號', 'domain.recognition': '識別增強', - 'section.running': '執行狀態', 'section.oneTime': '單次操作', 'section.schedule': '共用週期', - 'section.download': '下載任務處理', 'section.timeout': '逾時與重試', 'section.cleanup': '訂閱記錄清理', - 'section.pending': '待定策略', 'section.tvDecision': '影集判定', 'section.autoPause': '自動暫停', - 'section.airing': '上映與播出窗口', 'section.noDownload': '無下載處理', + 'domain.completionGuard': '完結守衛模式', + 'domain.pending': '待定增強', + 'domain.pause': '暫停最佳化', + 'domain.bestVersion': '自動洗版', + 'domain.download': '下載管理', + 'domain.verify': '完成後驗證', + 'domain.siteTotal': '站點集數探測', + 'domain.siteCompletion': '站點完結訊號', + 'domain.recognition': '識別增強', + 'section.running': '執行狀態', + 'section.oneTime': '單次操作', + 'section.schedule': '共用週期', + 'section.download': '下載任務處理', + 'section.timeout': '逾時與重試', + 'section.cleanup': '訂閱記錄清理', + 'section.pending': '待定策略', + 'section.tvDecision': '影集判定', + 'section.autoPause': '自動暫停', + 'section.airing': '上映與播出窗口', + 'section.noDownload': '無下載處理', 'section.siteProbe': '站點集數探測', - 'section.pausedProbe': '暫停訂閱補搜', 'section.bestVersionScope': '洗版範圍', - 'section.backfill': '轉換與回填', 'section.guard': '守衛訊號', 'section.cadence': '播出節奏', - 'section.correction': '修正與釋放', 'section.recognition': '識別策略', 'section.custom': '自訂規則', + 'section.pausedProbe': '暫停訂閱補搜', + 'section.bestVersionScope': '洗版範圍', + 'section.backfill': '轉換與回填', + 'section.guard': '守衛訊號', + 'section.cadence': '播出節奏', + 'section.correction': '修正與釋放', + 'section.recognition': '識別策略', + 'section.custom': '自訂規則', }, 'en-US': { 'config.changes': 'Changes', @@ -148,26 +178,53 @@ const messages: Record> = { 'config.aboutPlugin': 'About plugin', 'config.aboutDescription': 'Manage subscriptions across scenarios and throughout their lifecycle', 'config.viewDocs': 'View docs', - 'config.plugin': 'Plugins', 'config.settings': 'Plugin settings', 'config.selectGroup': 'Select settings group', - 'config.done': 'Done', 'config.edit': 'Edit', - 'config.decrease': 'Decrease {label}', 'config.increase': 'Increase {label}', 'config.editLabel': 'Edit {label}', + 'config.plugin': 'Plugins', + 'config.settings': 'Plugin settings', + 'config.selectGroup': 'Select settings group', + 'config.done': 'Done', + 'config.edit': 'Edit', + 'config.decrease': 'Decrease {label}', + 'config.increase': 'Increase {label}', + 'config.editLabel': 'Edit {label}', 'config.yamlTitle': 'Custom recognition rules', - 'config.runtime': 'Runtime summary', 'config.runtimeLoading': 'Loading runtime summary', - 'config.runtimeUnavailable': 'Runtime summary unavailable', 'config.pendingCount': 'Pending subscriptions', - 'config.monitoredCount': 'Downloads', 'config.enabled': 'Enabled', 'config.off': 'Off', + 'config.runtime': 'Runtime summary', + 'config.runtimeLoading': 'Loading runtime summary', + 'config.runtimeUnavailable': 'Runtime summary unavailable', + 'config.pendingCount': 'Pending subscriptions', + 'config.monitoredCount': 'Downloads', + 'config.enabled': 'Enabled', + 'config.off': 'Off', 'config.cronPlaceholder': '5-field CRON expression', 'config.title': 'Subscribe Assistant (Enhanced)', - 'domain.completionGuard': 'Completion guard mode', 'domain.pending': 'Pending enhancement', 'domain.pause': 'Pause optimization', - 'domain.bestVersion': 'Automatic upgrades', 'domain.download': 'Download management', 'domain.verify': 'Post-completion verification', - 'domain.siteTotal': 'Site episode probe', 'domain.siteCompletion': 'Site completion signal', 'domain.recognition': 'Recognition', - 'section.running': 'Runtime state', 'section.oneTime': 'One-time actions', 'section.schedule': 'Shared schedules', - 'section.download': 'Download handling', 'section.timeout': 'Timeouts and retries', 'section.cleanup': 'Subscription cleanup', - 'section.pending': 'Pending policy', 'section.tvDecision': 'TV decisions', 'section.autoPause': 'Automatic pause', - 'section.airing': 'Release and airing windows', 'section.noDownload': 'No-download handling', + 'domain.completionGuard': 'Completion guard mode', + 'domain.pending': 'Pending enhancement', + 'domain.pause': 'Pause optimization', + 'domain.bestVersion': 'Automatic upgrades', + 'domain.download': 'Download management', + 'domain.verify': 'Post-completion verification', + 'domain.siteTotal': 'Site episode probe', + 'domain.siteCompletion': 'Site completion signal', + 'domain.recognition': 'Recognition', + 'section.running': 'Runtime state', + 'section.oneTime': 'One-time actions', + 'section.schedule': 'Shared schedules', + 'section.download': 'Download handling', + 'section.timeout': 'Timeouts and retries', + 'section.cleanup': 'Subscription cleanup', + 'section.pending': 'Pending policy', + 'section.tvDecision': 'TV decisions', + 'section.autoPause': 'Automatic pause', + 'section.airing': 'Release and airing windows', + 'section.noDownload': 'No-download handling', 'section.siteProbe': 'Site episode probe', - 'section.pausedProbe': 'Paused subscription search', 'section.bestVersionScope': 'Best-version scope', - 'section.backfill': 'Conversion and backfill', 'section.guard': 'Guard signals', 'section.cadence': 'Airing cadence', - 'section.correction': 'Correction and release', 'section.recognition': 'Recognition policy', 'section.custom': 'Custom rules', + 'section.pausedProbe': 'Paused subscription search', + 'section.bestVersionScope': 'Best-version scope', + 'section.backfill': 'Conversion and backfill', + 'section.guard': 'Guard signals', + 'section.cadence': 'Airing cadence', + 'section.correction': 'Correction and release', + 'section.recognition': 'Recognition policy', + 'section.custom': 'Custom rules', }, } @@ -176,20 +233,44 @@ export function t(localeSource: LocaleSource, key: string, params: TranslationPa const locale = normalizeLocale(localeSource) const template = messages[locale][key] ?? messages['zh-CN'][key] if (!template) throw new Error(`Missing translation key: ${key}`) - return template.replace(/\{(\w+)\}/g, (match, name: string) => ( - Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match - )) + return template.replace(/\{(\w+)\}/g, (match, name: string) => + Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match, + ) } -const groupTranslations: Record = { - global: { tw: ['全域執行', '外掛開關、通知、單次操作與共用週期'], en: ['General', 'Plugin state, notifications, one-time actions, and shared schedules'] }, - cleanup: { tw: ['訂閱清理', '下載監控、刪除種子、Tracker 與整理記錄清理'], en: ['Cleanup', 'Download monitoring, torrent removal, Tracker rules, and history cleanup'] }, - pending: { tw: ['訂閱待定', '下載中或集數目標尚未穩定時保持待定'], en: ['Pending', 'Keep subscriptions pending while downloads or episode targets are unsettled'] }, - pause: { tw: ['訂閱暫停', '依使用者、播出窗口與無下載策略暫停訂閱'], en: ['Pause', 'Pause subscriptions by user, release window, or no-download policy'] }, - completion: { tw: ['訂閱補全', '站點集數探測與暫停訂閱補搜'], en: ['Completion', 'Site episode probes and paused subscription searches'] }, - bestVersion: { tw: ['訂閱洗版', '洗版範圍、時限、回填與分集轉全集'], en: ['Best version', 'Upgrade scope, time limits, backfill, and episode-to-season conversion'] }, - guard: { tw: ['完結訊號', '完結守衛、站點證據、波動節奏與自動修正'], en: ['Completion guard', 'Completion checks, site evidence, cadence, and automatic correction'] }, - recognition: { tw: ['識別增強', '候選准入、通知、二次識別與自訂策略'], en: ['Recognition', 'Candidate checks, notifications, re-identification, and custom policies'] }, +const groupTranslations: Record = { + global: { + tw: ['全域執行', '外掛開關、通知、單次操作與共用週期'], + en: ['General', 'Plugin state, notifications, one-time actions, and shared schedules'], + }, + cleanup: { + tw: ['訂閱清理', '下載監控、刪除種子、Tracker 與整理記錄清理'], + en: ['Cleanup', 'Download monitoring, torrent removal, Tracker rules, and history cleanup'], + }, + pending: { + tw: ['訂閱待定', '下載中或集數目標尚未穩定時保持待定'], + en: ['Pending', 'Keep subscriptions pending while downloads or episode targets are unsettled'], + }, + pause: { + tw: ['訂閱暫停', '依使用者、播出窗口與無下載策略暫停訂閱'], + en: ['Pause', 'Pause subscriptions by user, release window, or no-download policy'], + }, + completion: { + tw: ['訂閱補全', '站點集數探測與暫停訂閱補搜'], + en: ['Completion', 'Site episode probes and paused subscription searches'], + }, + bestVersion: { + tw: ['訂閱洗版', '洗版範圍、時限、回填與分集轉全集'], + en: ['Best version', 'Upgrade scope, time limits, backfill, and episode-to-season conversion'], + }, + guard: { + tw: ['完結訊號', '完結守衛、站點證據、波動節奏與自動修正'], + en: ['Completion guard', 'Completion checks, site evidence, cadence, and automatic correction'], + }, + recognition: { + tw: ['識別增強', '候選准入、通知、二次識別與自訂策略'], + en: ['Recognition', 'Candidate checks, notifications, re-identification, and custom policies'], + }, } type EnglishFieldText = readonly [label: string, hint: string] @@ -198,110 +279,408 @@ const englishFields: Record = { enabled: ['Enable plugin', 'Activate the plugin and register its scheduled tasks'], notify: ['Send notifications', 'Send notifications when relevant events occur'], onlyonce: ['Run once now', 'Run a full inspection after saving, then reset automatically'], - reset_task: ['Reset data', 'Reset all pending, paused, and monitored task data after saving, then reset automatically'], - auto_check_interval_minutes: ['General check interval (minutes)', 'Interval for site sampling, pending release, no-download handling, and cleanup'], + reset_task: [ + 'Reset data', + 'Reset all pending, paused, and monitored task data after saving, then reset automatically', + ], + auto_check_interval_minutes: [ + 'General check interval (minutes)', + 'Interval for site sampling, pending release, no-download handling, and cleanup', + ], download_check_interval_minutes: ['Download check interval (minutes)', 'How often download task status is checked'], meta_check_interval_hours: ['Metadata check interval (hours)', 'How often subscription metadata is reviewed'], best_version_cron: ['Best-version schedule', 'CRON schedule for best-version checks, for example 0 15 * * *'], download_monitor_enabled: ['Remove stalled downloads', 'Automatically remove subscription torrents that time out'], manual_delete_listen: ['Watch manual torrent removal', 'Record torrents manually removed by the user'], - tracker_response_listen: ['Watch Tracker response keywords', 'Remove torrents when a configured Tracker response keyword matches'], + tracker_response_listen: [ + 'Watch Tracker response keywords', + 'Remove torrents when a configured Tracker response keyword matches', + ], auto_search_when_delete: ['Search after removal', 'Trigger a completion search after removing a torrent'], skip_deletion: ['Skip recently removed releases', 'Avoid downloading recently removed torrents again'], - download_timeout_minutes: ['Download timeout (minutes)', 'Observation window used to detect downloads with insufficient progress'], - download_progress_threshold: ['Download progress threshold', 'Remove only when progress increases by less than N% during the timeout window'], - download_queue_grace_multiplier: ['Download queue grace multiplier', 'Allow N additional timeout windows while the downloader explicitly reports the task as queued; 0 disables the grace period'], - download_retry_limit: ['Consecutive timeout limit', 'Keep the torrent and notify after N consecutive low-progress timeouts'], + download_timeout_minutes: [ + 'Download timeout (minutes)', + 'Observation window used to detect downloads with insufficient progress', + ], + download_progress_threshold: [ + 'Download progress threshold', + 'Remove only when progress increases by less than N% during the timeout window', + ], + download_queue_grace_multiplier: [ + 'Download queue grace multiplier', + 'Allow N additional timeout windows while the downloader explicitly reports the task as queued; 0 disables the grace period', + ], + download_retry_limit: [ + 'Consecutive timeout limit', + 'Keep the torrent and notify after N consecutive low-progress timeouts', + ], delete_exclude_tags: ['Excluded tags', 'Comma-separated tags that must not be processed'], - default_tracker_response: ['Tracker response keywords', 'One keyword per line; case-insensitive regular expressions are supported'], - delete_record_retention_hours: ['Removal history retention (hours)', 'Periodically remove deletion records older than N hours'], - subscription_cleanup_history_type: ['Cleanup media scope', 'Media types whose old transfer records and files are removed before download'], - subscription_cleanup_history_scenes: ['Cleanup trigger scenarios', 'Choose which subscription download scenarios trigger cleanup'], - recognition_guard_mode: ['Recognition mode', 'Review whether a candidate matches the subscription target before automatic download'], + default_tracker_response: [ + 'Tracker response keywords', + 'One keyword per line; case-insensitive regular expressions are supported', + ], + delete_record_retention_hours: [ + 'Removal history retention (hours)', + 'Periodically remove deletion records older than N hours', + ], + subscription_cleanup_history_type: [ + 'Cleanup media scope', + 'Media types whose old transfer records and files are removed before download', + ], + subscription_cleanup_history_scenes: [ + 'Cleanup trigger scenarios', + 'Choose which subscription download scenarios trigger cleanup', + ], + recognition_guard_mode: [ + 'Recognition mode', + 'Review whether a candidate matches the subscription target before automatic download', + ], recognition_guard_notify: ['Recognition notifications', 'Control recognition messages without affecting audit logs'], - recognition_guard_notify_interval: ['Notification rate limit (seconds)', 'Minimum interval for the same subscription, action, and reason'], + recognition_guard_notify_interval: [ + 'Notification rate limit (seconds)', + 'Minimum interval for the same subscription, action, and reason', + ], recognition_guard_tmdb_recheck_mode: ['Secondary recognition', 'Control when secondary recognition is performed'], - recognition_guard_cache_maxsize: ['Recognition cache size', 'Cache secondary recognition results to avoid duplicate requests'], - recognition_guard_custom_config: ['Custom recognition rules', 'Edit only when built-in rules are insufficient; leave empty to inherit the current mode'], - pending_enhanced_enabled: ['Automatically pend TV subscriptions', 'Mark TV subscriptions pending to avoid completing them too early'], + recognition_guard_cache_maxsize: [ + 'Recognition cache size', + 'Cache secondary recognition results to avoid duplicate requests', + ], + recognition_guard_custom_config: [ + 'Custom recognition rules', + 'Edit only when built-in rules are insufficient; leave empty to inherit the current mode', + ], + pending_enhanced_enabled: [ + 'Automatically pend TV subscriptions', + 'Mark TV subscriptions pending to avoid completing them too early', + ], pending_download_enabled: ['Pend active downloads', 'Keep subscriptions pending while downloads are in progress'], auto_tv_pending_days: ['TV pending days', 'Keep pending before the release date plus N days; 0 disables this rule'], - auto_tv_pending_episodes: ['TV pending episode count', 'Keep pending when the episode count is at or below this value; 0 disables this rule'], - pending_use_volatility: ['Use change rate for pending', 'Pend early when the total episode count changes near completion'], + auto_tv_pending_episodes: [ + 'TV pending episode count', + 'Keep pending when the episode count is at or below this value; 0 disables this rule', + ], + pending_use_volatility: [ + 'Use change rate for pending', + 'Pend early when the total episode count changes near completion', + ], pause_enhanced_enabled: ['Automatically pause subscriptions', 'Pause subscriptions to avoid unnecessary requests'], - auto_pause_users: ['Auto-pause users (comma-separated)', 'Pause new subscriptions from listed users; leave empty to disable'], - airing_pause_days: ['Upcoming episode pause days', 'Pause when the next episode is more than N days away; 0 disables this rule'], - movie_air_pause_days: ['Movie release pause days', 'Pause until N days before the movie release date; 0 disables this rule'], + auto_pause_users: [ + 'Auto-pause users (comma-separated)', + 'Pause new subscriptions from listed users; leave empty to disable', + ], + airing_pause_days: [ + 'Upcoming episode pause days', + 'Pause when the next episode is more than N days away; 0 disables this rule', + ], + movie_air_pause_days: [ + 'Movie release pause days', + 'Pause until N days before the movie release date; 0 disables this rule', + ], tv_air_pause_days: ['TV premiere pause days', 'Pause until N days before the TV premiere date; 0 disables this rule'], - movie_no_download_days: ['Movie no-download days', 'Apply the selected policy when no movie download occurs within N days; 0 disables it'], - tv_no_download_days: ['TV no-download days', 'Apply the selected policy when no TV download occurs within N days; 0 disables it'], + movie_no_download_days: [ + 'Movie no-download days', + 'Apply the selected policy when no movie download occurs within N days; 0 disables it', + ], + tv_no_download_days: [ + 'TV no-download days', + 'Apply the selected policy when no TV download occurs within N days; 0 disables it', + ], no_download_actions: ['No-download actions', 'Choose the actions to apply when no download is found'], - site_total_probe_enabled: ['Probe site episode totals', 'Use cached site releases to detect an incomplete episode target'], + site_total_probe_enabled: [ + 'Probe site episode totals', + 'Use cached site releases to detect an incomplete episode target', + ], paused_probe_reasons: ['Paused search scenarios', 'Choose pause reasons that allow low-frequency searches'], - paused_probe_min_pause_days: ['Search after N paused days', 'Start searching after this many paused days; 0 disables it'], - paused_probe_interval_hours: ['Search interval (hours)', 'Minimum interval between two searches for the same subscription'], - best_version_type: ['Best-version type', 'Select media types for automatic upgrades; Off disables creation and checks'], - best_version_movie_remaining_days: ['Movie upgrade time limit (days)', 'Stop movie upgrade subscriptions after this period; 0 means unlimited'], - best_version_tv_remaining_days: ['TV upgrade time limit (days)', 'Stop TV upgrade subscriptions after this period; 0 means unlimited'], - best_version_episode_to_full: ['Convert episodes to full season', 'Switch from episode upgrades to a full-season upgrade when the target is met'], - best_version_backfill_enabled: ['Backfill existing episodes', 'Backfill library episodes when creating or converting an episode upgrade'], - backfill_best_version_now: ['Scan and backfill now', 'Backfill existing episode-upgrade subscriptions after saving, then reset automatically'], - completion_guard_mode: ['Completion guard mode', 'Choose the review strength used before completion; Balanced is the default'], - site_completion_evidence_enabled: ['Use site completion evidence', 'Use site release titles as supporting completion evidence'], + paused_probe_min_pause_days: [ + 'Search after N paused days', + 'Start searching after this many paused days; 0 disables it', + ], + paused_probe_interval_hours: [ + 'Search interval (hours)', + 'Minimum interval between two searches for the same subscription', + ], + best_version_type: [ + 'Best-version type', + 'Select media types for automatic upgrades; Off disables creation and checks', + ], + best_version_movie_remaining_days: [ + 'Movie upgrade time limit (days)', + 'Stop movie upgrade subscriptions after this period; 0 means unlimited', + ], + best_version_tv_remaining_days: [ + 'TV upgrade time limit (days)', + 'Stop TV upgrade subscriptions after this period; 0 means unlimited', + ], + best_version_episode_to_full: [ + 'Convert episodes to full season', + 'Switch from episode upgrades to a full-season upgrade when the target is met', + ], + best_version_backfill_enabled: [ + 'Backfill existing episodes', + 'Backfill library episodes when creating or converting an episode upgrade', + ], + backfill_best_version_now: [ + 'Scan and backfill now', + 'Backfill existing episode-upgrade subscriptions after saving, then reset automatically', + ], + completion_guard_mode: [ + 'Completion guard mode', + 'Choose the review strength used before completion; Balanced is the default', + ], + site_completion_evidence_enabled: [ + 'Use site completion evidence', + 'Use site release titles as supporting completion evidence', + ], volatility_enabled: ['Episode-count change signal', 'Treat recent total episode count changes as unstable'], volatility_window_days: ['Change-rate window (days)', 'Number of days used to measure total episode count changes'], - cadence_enabled: ['Airing cadence signal', 'Estimate the waiting period from airing intervals without directly marking completion'], + cadence_enabled: [ + 'Airing cadence signal', + 'Estimate the waiting period from airing intervals without directly marking completion', + ], cadence_multiplier: ['Cadence window multiplier', 'Increase the estimated waiting period; higher values wait longer'], - cadence_min_window_days: ['Minimum cadence window (days)', 'The estimated waiting period cannot be shorter than this value'], - cadence_min_episodes: ['Minimum episodes for cadence', 'Calculate airing intervals only after this many episodes have aired'], + cadence_min_window_days: [ + 'Minimum cadence window (days)', + 'The estimated waiting period cannot be shorter than this value', + ], + cadence_min_episodes: [ + 'Minimum episodes for cadence', + 'Calculate airing intervals only after this many episodes have aired', + ], season_cooldown_days: ['Season cooldown (days)', 'Continue observing for this many days after the last episode airs'], - verify_enabled: ['Automatic correction', 'Recheck completed episode counts and rebuild subscriptions when the count increases'], + verify_enabled: [ + 'Automatic correction', + 'Recheck completed episode counts and rebuild subscriptions when the count increases', + ], verify_interval_hours: ['Correction interval (hours)', 'Interval for rechecking episode counts after completion'], - verify_retention_days: ['Snapshot retention (days)', 'Retain completion snapshots for this many days; default is 180'], - timeout_release_days: ['Pre-completion observation days', 'Maximum number of days allowed for pre-completion observation'], - timeout_cadence_acceleration: ['Accelerate release by cadence', 'Shorten the observation period after the cadence waiting window ends'], + verify_retention_days: [ + 'Snapshot retention (days)', + 'Retain completion snapshots for this many days; default is 180', + ], + timeout_release_days: [ + 'Pre-completion observation days', + 'Maximum number of days allowed for pre-completion observation', + ], + timeout_cadence_acceleration: [ + 'Accelerate release by cadence', + 'Shorten the observation period after the cadence waiting window ends', + ], } const traditionalPhrases: Array<[string, string]> = [ - ['插件', '外掛'], ['启用', '啟用'], ['发送', '傳送'], ['通知', '通知'], ['运行', '執行'], - ['重置', '重設'], ['数据', '資料'], ['检查', '檢查'], ['周期', '週期'], ['下载', '下載'], - ['订阅', '訂閱'], ['删除', '刪除'], ['记录', '記錄'], ['监听', '監聽'], ['关键字', '關鍵字'], - ['进度', '進度'], ['连续', '連續'], ['时', '時'], ['分钟', '分鐘'], ['小时', '小時'], - ['自动', '自動'], ['状态', '狀態'], ['配置', '設定'], ['识别', '識別'], ['增强', '增強'], - ['自定义', '自訂'], ['剧集', '影集'], ['电影', '電影'], ['上映', '上映'], ['暂停', '暫停'], - ['用户', '使用者'], ['选择', '選擇'], ['范围', '範圍'], ['场景', '情境'], ['关闭', '關閉'], - ['全部', '全部'], ['严格', '嚴格'], ['宽松', '寬鬆'], ['平衡', '平衡'], ['仅', '僅'], - ['完结', '完結'], ['信号', '訊號'], ['纠错', '修正'], ['变更', '變更'], ['节奏', '節奏'], - ['间隔', '間隔'], ['默认', '預設'], ['目标', '目標'], ['满足', '符合'], ['转换', '轉換'], - ['转', '轉'], ['扫描', '掃描'], ['存量', '既有'], ['回填', '回填'], ['媒体库', '媒體庫'], - ['整理', '整理'], ['文件', '檔案'], ['多个', '多個'], ['为空', '留空'], ['表示', '表示'], - ['开启', '開啟'], ['发生', '發生'], ['复核', '複核'], ['触发', '觸發'], ['清理', '清理'], - ['待定', '待定'], ['完成', '完成'], ['总集数', '總集數'], ['集数', '集數'], ['天数', '天數'], - ['策略', '策略'], ['模式', '模式'], ['缓存', '快取'], ['大小', '大小'], ['支持', '支援'], - ['处于激活状态', '處於啟用狀態'], ['正则表达式', '正規表示式'], ['媒体类型', '媒體類型'], ['审计', '稽核'], ['消息推送', '訊息推送'], - ['站点', '站點'], ['搜索', '搜尋'], ['补搜', '補搜'], ['巡检', '巡檢'], ['种子', '種子'], - ['任务', '任務'], ['标签', '標籤'], ['请求', '請求'], ['名单', '名單'], ['候选', '候選'], - ['标题', '標題'], ['诊断', '診斷'], ['类型', '類型'], ['创建', '建立'], ['终止', '終止'], - ['守卫', '守衛'], ['统计', '統計'], ['判断', '判斷'], ['预计', '預計'], ['参与', '參與'], - ['观察', '觀察'], ['释放', '釋放'], ['结果', '結果'], ['动作', '動作'], ['原因', '原因'], - ['频', '頻'], ['秒数', '秒數'], ['资源', '資源'], ['辅助', '輔助'], ['不足', '不足'], - ['允许', '允許'], ['达到', '達到'], ['两次', '兩次'], ['轮数', '輪數'], ['提醒', '提醒'], - ['强度', '強度'], ['佐证', '佐證'], ['稳定', '穩定'], ['增加', '增加'], ['重新', '重新'], - ['最后', '最後'], ['继续', '繼續'], ['结束', '結束'], ['缩短', '縮短'], ['保存', '儲存'], - ['采样', '取樣'], ['补全', '補全'], ['手动', '手動'], ['跳过', '略過'], ['作为', '作為'], - ['低于', '低於'], ['视为', '視為'], ['一个', '一個'], ['大小写', '大小寫'], ['精准', '精準'], - ['入库', '入庫'], ['日志', '日誌'], ['明细', '明細'], ['覆盖', '覆蓋'], ['进行', '進行'], - ['设置', '設定'], ['等于', '等於'], ['参考', '參考'], ['意义', '意義'], ['逗号', '逗號'], - ['探测', '探測'], ['多少轮', '多少輪'], ['计算', '計算'], ['新建', '建立'], ['切换', '切換'], - ['于', '於'], ['视', '視'], ['采', '採'], ['补', '補'], ['删', '刪'], ['轮', '輪'], ['算', '算'], - ['后', '後'], ['会', '會'], ['将', '將'], ['处', '處'], ['为', '為'], ['与', '與'], ['发', '發'], - ['过', '過'], ['这', '這'], ['则', '則'], ['无', '無'], ['设', '設'], ['选', '選'], ['线', '線'], - ['响', '響'], ['应', '應'], ['种', '種'], ['从', '從'], ['开', '開'], ['进', '進'], ['间', '間'], ['数', '數'], - ['长', '長'], ['现', '現'], ['还', '還'], ['较', '較'], ['达', '達'], ['实', '實'], ['复', '複'], - ['对', '對'], ['内', '內'], ['样', '樣'], ['并', '並'], ['当', '當'], ['监', '監'], ['执', '執'], - ['检', '檢'], ['动', '動'], ['试', '試'], ['阈', '閾'], ['值', '值'], ['写', '寫'], ['号', '號'], - ['旧', '舊'], ['库', '庫'], ['坏', '壞'], ['记', '記'], ['覆', '覆'], ['标', '標'], ['变化', '變化'], - ['减', '減'], ['满', '滿'], ['少', '少'], ['低', '低'], ['冷却', '冷卻'], ['换', '換'], ['别', '別'], + ['插件', '外掛'], + ['启用', '啟用'], + ['发送', '傳送'], + ['通知', '通知'], + ['运行', '執行'], + ['重置', '重設'], + ['数据', '資料'], + ['检查', '檢查'], + ['周期', '週期'], + ['下载', '下載'], + ['订阅', '訂閱'], + ['删除', '刪除'], + ['记录', '記錄'], + ['监听', '監聽'], + ['关键字', '關鍵字'], + ['进度', '進度'], + ['连续', '連續'], + ['时', '時'], + ['分钟', '分鐘'], + ['小时', '小時'], + ['自动', '自動'], + ['状态', '狀態'], + ['配置', '設定'], + ['识别', '識別'], + ['增强', '增強'], + ['自定义', '自訂'], + ['剧集', '影集'], + ['电影', '電影'], + ['上映', '上映'], + ['暂停', '暫停'], + ['用户', '使用者'], + ['选择', '選擇'], + ['范围', '範圍'], + ['场景', '情境'], + ['关闭', '關閉'], + ['全部', '全部'], + ['严格', '嚴格'], + ['宽松', '寬鬆'], + ['平衡', '平衡'], + ['仅', '僅'], + ['完结', '完結'], + ['信号', '訊號'], + ['纠错', '修正'], + ['变更', '變更'], + ['节奏', '節奏'], + ['间隔', '間隔'], + ['默认', '預設'], + ['目标', '目標'], + ['满足', '符合'], + ['转换', '轉換'], + ['转', '轉'], + ['扫描', '掃描'], + ['存量', '既有'], + ['回填', '回填'], + ['媒体库', '媒體庫'], + ['整理', '整理'], + ['文件', '檔案'], + ['多个', '多個'], + ['为空', '留空'], + ['表示', '表示'], + ['开启', '開啟'], + ['发生', '發生'], + ['复核', '複核'], + ['触发', '觸發'], + ['清理', '清理'], + ['待定', '待定'], + ['完成', '完成'], + ['总集数', '總集數'], + ['集数', '集數'], + ['天数', '天數'], + ['策略', '策略'], + ['模式', '模式'], + ['缓存', '快取'], + ['大小', '大小'], + ['支持', '支援'], + ['处于激活状态', '處於啟用狀態'], + ['正则表达式', '正規表示式'], + ['媒体类型', '媒體類型'], + ['审计', '稽核'], + ['消息推送', '訊息推送'], + ['站点', '站點'], + ['搜索', '搜尋'], + ['补搜', '補搜'], + ['巡检', '巡檢'], + ['种子', '種子'], + ['任务', '任務'], + ['标签', '標籤'], + ['请求', '請求'], + ['名单', '名單'], + ['候选', '候選'], + ['标题', '標題'], + ['诊断', '診斷'], + ['类型', '類型'], + ['创建', '建立'], + ['终止', '終止'], + ['守卫', '守衛'], + ['统计', '統計'], + ['判断', '判斷'], + ['预计', '預計'], + ['参与', '參與'], + ['观察', '觀察'], + ['释放', '釋放'], + ['结果', '結果'], + ['动作', '動作'], + ['原因', '原因'], + ['频', '頻'], + ['秒数', '秒數'], + ['资源', '資源'], + ['辅助', '輔助'], + ['不足', '不足'], + ['允许', '允許'], + ['达到', '達到'], + ['两次', '兩次'], + ['轮数', '輪數'], + ['提醒', '提醒'], + ['强度', '強度'], + ['佐证', '佐證'], + ['稳定', '穩定'], + ['增加', '增加'], + ['重新', '重新'], + ['最后', '最後'], + ['继续', '繼續'], + ['结束', '結束'], + ['缩短', '縮短'], + ['保存', '儲存'], + ['采样', '取樣'], + ['补全', '補全'], + ['手动', '手動'], + ['跳过', '略過'], + ['作为', '作為'], + ['低于', '低於'], + ['视为', '視為'], + ['一个', '一個'], + ['大小写', '大小寫'], + ['精准', '精準'], + ['入库', '入庫'], + ['日志', '日誌'], + ['明细', '明細'], + ['覆盖', '覆蓋'], + ['进行', '進行'], + ['设置', '設定'], + ['等于', '等於'], + ['参考', '參考'], + ['意义', '意義'], + ['逗号', '逗號'], + ['探测', '探測'], + ['多少轮', '多少輪'], + ['计算', '計算'], + ['新建', '建立'], + ['切换', '切換'], + ['于', '於'], + ['视', '視'], + ['采', '採'], + ['补', '補'], + ['删', '刪'], + ['轮', '輪'], + ['算', '算'], + ['后', '後'], + ['会', '會'], + ['将', '將'], + ['处', '處'], + ['为', '為'], + ['与', '與'], + ['发', '發'], + ['过', '過'], + ['这', '這'], + ['则', '則'], + ['无', '無'], + ['设', '設'], + ['选', '選'], + ['线', '線'], + ['响', '響'], + ['应', '應'], + ['种', '種'], + ['从', '從'], + ['开', '開'], + ['进', '進'], + ['间', '間'], + ['数', '數'], + ['长', '長'], + ['现', '現'], + ['还', '還'], + ['较', '較'], + ['达', '達'], + ['实', '實'], + ['复', '複'], + ['对', '對'], + ['内', '內'], + ['样', '樣'], + ['并', '並'], + ['当', '當'], + ['监', '監'], + ['执', '執'], + ['检', '檢'], + ['动', '動'], + ['试', '試'], + ['阈', '閾'], + ['值', '值'], + ['写', '寫'], + ['号', '號'], + ['旧', '舊'], + ['库', '庫'], + ['坏', '壞'], + ['记', '記'], + ['覆', '覆'], + ['标', '標'], + ['变化', '變化'], + ['减', '減'], + ['满', '滿'], + ['少', '少'], + ['低', '低'], + ['冷却', '冷卻'], + ['换', '換'], + ['别', '別'], ] function toTraditional(text: string): string { @@ -309,20 +688,47 @@ function toTraditional(text: string): string { } const englishOptionTitles: Record = { - no: 'Off', off: 'Off', all: 'All', movie: 'Movies', tv: 'TV shows', tv_episode: 'TV shows (individual episodes)', - normal: 'Standard subscriptions', best_version: 'Best-version subscriptions', best_version_episode: 'Episode upgrades', - audit: 'Audit', loose: 'Relaxed', balanced: 'Balanced', strict: 'Strict', summary: 'Summary', detail: 'Details', - balanced_strict: 'Balanced and strict', pause_movie: 'Pause movie subscriptions', pause_tv: 'Pause TV subscriptions', - complete_movie: 'Complete movie subscriptions', complete_tv: 'Complete TV subscriptions', - delete_movie: 'Delete movie subscriptions', delete_tv: 'Delete TV subscriptions', no_download: 'No downloads', - pre_air: 'Before release', airing_gap: 'Airing gap', auto_user: 'User rule', external: 'External pause', notify: 'Notify only', + no: 'Off', + off: 'Off', + all: 'All', + movie: 'Movies', + tv: 'TV shows', + tv_episode: 'TV shows (individual episodes)', + normal: 'Standard subscriptions', + best_version: 'Best-version subscriptions', + best_version_episode: 'Episode upgrades', + audit: 'Audit', + loose: 'Relaxed', + balanced: 'Balanced', + strict: 'Strict', + summary: 'Summary', + detail: 'Details', + balanced_strict: 'Balanced and strict', + pause_movie: 'Pause movie subscriptions', + pause_tv: 'Pause TV subscriptions', + complete_movie: 'Complete movie subscriptions', + complete_tv: 'Complete TV subscriptions', + delete_movie: 'Delete movie subscriptions', + delete_tv: 'Delete TV subscriptions', + no_download: 'No downloads', + pre_air: 'Before release', + airing_gap: 'Airing gap', + auto_user: 'User rule', + external: 'External pause', + notify: 'Notify only', } -function localizedOptionTitle(locale: SupportedLocale, field: FieldMeta, value: string | number, source: string): string { +function localizedOptionTitle( + locale: SupportedLocale, + field: FieldMeta, + value: string | number, + source: string, +): string { if (locale === 'zh-CN') return source if (locale === 'zh-TW') return toTraditional(source) if (typeof value === 'number') { - if (field.key === 'auto_check_interval_minutes' || field.key === 'download_check_interval_minutes') return `${value} minutes` + if (field.key === 'auto_check_interval_minutes' || field.key === 'download_check_interval_minutes') + return `${value} minutes` if (field.key === 'meta_check_interval_hours') return `${value} hours` return String(value) } @@ -337,11 +743,8 @@ export function localizeGroups(localeSource: LocaleSource, source: readonly Grou return source.map(group => { const translation = groupTranslations[group.key] if (!translation) throw new Error(`Missing group translation: ${group.key}`) - const [title, summary] = locale === 'zh-CN' - ? [group.title, group.summary] - : locale === 'zh-TW' - ? translation.tw - : translation.en + const [title, summary] = + locale === 'zh-CN' ? [group.title, group.summary] : locale === 'zh-TW' ? translation.tw : translation.en return { ...group, title, summary } }) } diff --git a/plugins.v2/subscribeassistantenhanced/frontend/src/config/presentation.ts b/plugins.v2/subscribeassistantenhanced/frontend/src/config/presentation.ts index 7c34674a..bc5041b3 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/src/config/presentation.ts +++ b/plugins.v2/subscribeassistantenhanced/frontend/src/config/presentation.ts @@ -4,16 +4,40 @@ import type { SupportedLocale } from './i18n' const unitLabels: Record> = { 'zh-CN': { - count: '次', day: '天', episode: '集', hour: '小时', item: '条', minute: '分钟', - multiplier: '倍', percent: '%', round: '轮', second: '秒', + count: '次', + day: '天', + episode: '集', + hour: '小时', + item: '条', + minute: '分钟', + multiplier: '倍', + percent: '%', + round: '轮', + second: '秒', }, 'zh-TW': { - count: '次', day: '天', episode: '集', hour: '小時', item: '條', minute: '分鐘', - multiplier: '倍', percent: '%', round: '輪', second: '秒', + count: '次', + day: '天', + episode: '集', + hour: '小時', + item: '條', + minute: '分鐘', + multiplier: '倍', + percent: '%', + round: '輪', + second: '秒', }, 'en-US': { - count: 'x', day: 'd', episode: 'ep', hour: 'hr', item: 'items', minute: 'min', - multiplier: 'x', percent: '%', round: 'rounds', second: 'sec', + count: 'x', + day: 'd', + episode: 'ep', + hour: 'hr', + item: 'items', + minute: 'min', + multiplier: 'x', + percent: '%', + round: 'rounds', + second: 'sec', }, } @@ -37,5 +61,18 @@ export function numberFieldUnit(key: ConfigKey, locale: SupportedLocale = 'zh-CN /** 数字字段的单位由步进器展示,标题只保留业务名称。 */ export function displayFieldLabel(field: FieldMeta): string { if (field.kind !== 'number') return field.label - return field.label.replace(/\s*[((][^))]+[))]\s*/g, '').trim() + let label = field.label + for (const [opening, closing] of [ + ['(', ')'], + ['(', ')'], + ] as const) { + let start = label.indexOf(opening) + while (start >= 0) { + const end = label.indexOf(closing, start + opening.length) + if (end < 0) break + label = `${label.slice(0, start)}${label.slice(end + closing.length)}` + start = label.indexOf(opening) + } + } + return label.trim() } diff --git a/plugins.v2/subscribeassistantenhanced/frontend/src/config/values.ts b/plugins.v2/subscribeassistantenhanced/frontend/src/config/values.ts index aabc769d..5c9014eb 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/src/config/values.ts +++ b/plugins.v2/subscribeassistantenhanced/frontend/src/config/values.ts @@ -48,9 +48,7 @@ function normalizeStringArray(defaultValue: string[], incoming: unknown): string /** Host 配置来自动态 JSON;这里只接受稳定键并按默认值类型重建完整持久化契约。 */ export function normalizeSaeConfig(input: unknown): SaeConfig { const source = - input !== null && typeof input === 'object' && !Array.isArray(input) - ? (input as Record) - : {} + input !== null && typeof input === 'object' && !Array.isArray(input) ? (input as Record) : {} const entries = (Object.keys(configDefaults) as ConfigKey[]).map(key => { const defaultValue = configDefaults[key] const incoming = source[key] diff --git a/plugins.v2/subscribeassistantenhanced/frontend/vite.config.ts b/plugins.v2/subscribeassistantenhanced/frontend/vite.config.ts index 04d1dc79..0bbf9f7c 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/vite.config.ts +++ b/plugins.v2/subscribeassistantenhanced/frontend/vite.config.ts @@ -1,5 +1,3 @@ -/// - import { fileURLToPath } from 'node:url' import vue from '@vitejs/plugin-vue' @@ -7,9 +5,9 @@ import federation from '@originjs/vite-plugin-federation' import { defineConfig, normalizePath, type Plugin } from 'vite' import { configDefaults } from 'vitest/config' -const TEST_ROOT = normalizePath(fileURLToPath( - new URL('../../../tests/v2/subscribeassistantenhanced/frontend', import.meta.url), -)) +const TEST_ROOT = normalizePath( + fileURLToPath(new URL('../../../tests/v2/subscribeassistantenhanced/frontend', import.meta.url)), +) const REPOSITORY_ROOT = normalizePath(fileURLToPath(new URL('../../..', import.meta.url))) const isTestMode = (mode: string): boolean => mode === 'test' || process.env.VITEST === 'true' @@ -29,7 +27,10 @@ function cleanFederationAssets(): Plugin { const remoteEntry = bundle['assets/remoteEntry.js'] if (remoteEntry?.type === 'chunk') { // 规范化生成器空白,确保重复构建通过仓库检查。 - remoteEntry.code = remoteEntry.code.replace(/[ \t]+$/gm, '') + remoteEntry.code = remoteEntry.code + .split('\n') + .map(line => line.trimEnd()) + .join('\n') } }, } diff --git a/plugins.v2/subscribeassistantenhanced/frontend/yarn.lock b/plugins.v2/subscribeassistantenhanced/frontend/yarn.lock index 701fda26..85b37059 100644 --- a/plugins.v2/subscribeassistantenhanced/frontend/yarn.lock +++ b/plugins.v2/subscribeassistantenhanced/frontend/yarn.lock @@ -213,6 +213,90 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== +"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.2", "@eslint-community/regexpp@^4.8.0": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.23.5": + version "0.23.5" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95" + integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA== + dependencies: + "@eslint/object-schema" "^3.0.5" + debug "^4.3.1" + minimatch "^10.2.4" + +"@eslint/config-helpers@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.6.0.tgz#ef9a36881d39dfd5dbeac22b0da997fabfb08b03" + integrity sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA== + dependencies: + "@eslint/core" "^1.2.1" + +"@eslint/core@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce" + integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/js@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583" + integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA== + +"@eslint/object-schema@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091" + integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== + +"@eslint/plugin-kit@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz#4b0962f3f2c7ce8bc98b3ecfe34525c09d2cb729" + integrity sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A== + dependencies: + "@eslint/core" "^1.2.1" + levn "^0.4.1" + +"@humanfs/core@^0.19.2": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.2.tgz#a8272ca03b2acf492670222b2320b6c421bfde60" + integrity sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== + dependencies: + "@humanfs/types" "^0.15.0" + +"@humanfs/node@^0.16.6": + version "0.16.8" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.8.tgz#8f800cccc13f4f8cd3116e2d9c0a94939da3e3ed" + integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== + dependencies: + "@humanfs/core" "^0.19.2" + "@humanfs/types" "^0.15.0" + "@humanwhocodes/retry" "^0.4.0" + +"@humanfs/types@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@humanfs/types/-/types-0.15.0.tgz#f2a09f62012390b2bff3fc6fb248ddec8c09a090" + integrity sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + "@inquirer/ansi@^2.0.7": version "2.0.7" resolved "https://registry.yarnpkg.com/@inquirer/ansi/-/ansi-2.0.7.tgz#86de22810cac3ed406ec10f8d66016815b8226b4" @@ -528,11 +612,21 @@ resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== -"@types/estree@1.0.9", "@types/estree@^1.0.0": +"@types/esrecurse@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec" + integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + +"@types/estree@1.0.9", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@^1.0.8": version "1.0.9" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/node@*": version "26.1.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.1.tgz#bad758d601e97d6cf457d204ee76a35fce7bd119" @@ -552,6 +646,102 @@ resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.6.tgz#66748315cc9a96d63403baa8671b2c124f8633aa" integrity sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA== +"@typescript-eslint/eslint-plugin@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz#71a0c3d5f8a5e6c5dfdb4f0f04bd1bfb572d5e24" + integrity sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.64.0" + "@typescript-eslint/type-utils" "8.64.0" + "@typescript-eslint/utils" "8.64.0" + "@typescript-eslint/visitor-keys" "8.64.0" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.5.0" + +"@typescript-eslint/parser@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.64.0.tgz#c9864a1cc28a13ff29a7314fbdef0528bb122f72" + integrity sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw== + dependencies: + "@typescript-eslint/scope-manager" "8.64.0" + "@typescript-eslint/types" "8.64.0" + "@typescript-eslint/typescript-estree" "8.64.0" + "@typescript-eslint/visitor-keys" "8.64.0" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.64.0.tgz#14c4e29390d7325a7f8a1218c2788fd649b85da6" + integrity sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.64.0" + "@typescript-eslint/types" "^8.64.0" + debug "^4.4.3" + +"@typescript-eslint/scope-manager@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz#d45f15304a94c85c39db317b717b158fb6259958" + integrity sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w== + dependencies: + "@typescript-eslint/types" "8.64.0" + "@typescript-eslint/visitor-keys" "8.64.0" + +"@typescript-eslint/tsconfig-utils@8.64.0", "@typescript-eslint/tsconfig-utils@^8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz#c62ac8ea9173c3cac8b38b8e66e30a046b548851" + integrity sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw== + +"@typescript-eslint/type-utils@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz#106fa7d58cf9cf7758f3dd8e426ac8237eceacf3" + integrity sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg== + dependencies: + "@typescript-eslint/types" "8.64.0" + "@typescript-eslint/typescript-estree" "8.64.0" + "@typescript-eslint/utils" "8.64.0" + debug "^4.4.3" + ts-api-utils "^2.5.0" + +"@typescript-eslint/types@8.64.0", "@typescript-eslint/types@^8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.64.0.tgz#b41f8ef5dd40616908658b991197a9d486cda60b" + integrity sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA== + +"@typescript-eslint/typescript-estree@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz#b8d51255e2d726eb4bd80d397a4fb4170c02eecc" + integrity sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA== + dependencies: + "@typescript-eslint/project-service" "8.64.0" + "@typescript-eslint/tsconfig-utils" "8.64.0" + "@typescript-eslint/types" "8.64.0" + "@typescript-eslint/visitor-keys" "8.64.0" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.5.0" + +"@typescript-eslint/utils@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.64.0.tgz#98bb2010cfb754b41985b9c93e6e8b3dcd7bd600" + integrity sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.64.0" + "@typescript-eslint/types" "8.64.0" + "@typescript-eslint/typescript-estree" "8.64.0" + +"@typescript-eslint/visitor-keys@8.64.0": + version "8.64.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz#7a08421d10e54960733352cd7c95fab1784e8473" + integrity sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw== + dependencies: + "@typescript-eslint/types" "8.64.0" + eslint-visitor-keys "^5.0.0" + "@vitejs/plugin-vue@^5.0.4": version "5.2.4" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz#9e8a512eb174bfc2a333ba959bbf9de428d89ad8" @@ -773,11 +963,31 @@ abbrev@^2.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.16.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.17.0.tgz#1785adb84faf8d8add10369b93826fc2bd08f1fe" + integrity sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg== + agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.4" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== +ajv@^6.14.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" + integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + alien-signals@^1.0.3: version "1.0.13" resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-1.0.13.tgz#8d6db73462f742ee6b89671fbd8c37d0b1727a7e" @@ -861,6 +1071,11 @@ balanced-match@^4.0.2: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^2.0.2: version "2.1.2" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.2.tgz#0bba2271feb7d458b0d31ad13625aaa4754431e2" @@ -875,6 +1090,16 @@ brace-expansion@^5.0.5: dependencies: balanced-match "^4.0.2" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +bytes@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + cac@^6.7.14: version "6.7.14" resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" @@ -988,6 +1213,11 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + cssstyle@^4.2.1: version "4.6.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.6.0.tgz#ea18007024e3167f4f105315f3ec2d982bf48ed9" @@ -1014,7 +1244,7 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== -debug@4, debug@^4.1.1, debug@^4.3.4, debug@^4.4.1: +debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.1, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -1055,6 +1285,11 @@ deep-equal@^2.0.5: which-collection "^1.0.1" which-typed-array "^1.1.13" +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -1198,6 +1433,126 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-sonarjs@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-4.2.0.tgz#34fb67be392c3f49875ce5405e02d62f2f860304" + integrity sha512-bqADfuNtTL7VK6RU29eoiFTtaaBKIpVPuX3bOl+rBpWSBa0zIBVZlqZNZQjfP6s4iXkAJokv5IsD8OsACkwApg== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + builtin-modules "^3.3.0" + bytes "^3.1.2" + functional-red-black-tree "^1.0.1" + globals "^17.7.0" + jsx-ast-utils-x "^0.1.0" + lodash.merge "^4.6.2" + minimatch "^10.2.5" + scslre "^0.3.0" + semver "^7.8.5" + ts-api-utils "^2.5.0" + typescript ">=5 <6.1.0" + yaml "^2.9.0" + +eslint-plugin-vue@^10.9.2: + version "10.10.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-10.10.0.tgz#e957b56853dea54e738136d849d85929954bdf3d" + integrity sha512-dL9x9rBHqqNcByWiLOHK6L0SB97V82/NC0cZRn9cXPjM7pCuWlpQQP9bFH4vjBv80ej1ZpzAkuD8zWH1o9bZbA== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + natural-compare "^1.4.0" + nth-check "^2.1.1" + postcss-selector-parser "^7.1.4" + semver "^7.8.5" + xml-name-validator "^5.0.0" + +"eslint-scope@^8.2.0 || ^9.0.0", eslint-scope@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802" + integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== + dependencies: + "@types/esrecurse" "^4.3.1" + "@types/estree" "^1.0.8" + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +"eslint-visitor-keys@^4.2.0 || ^5.0.0", eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + +eslint@^10.7.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.7.0.tgz#cd3b8022f3b1e3b183760d90dfc58e9d3644106b" + integrity sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.2" + "@eslint/config-array" "^0.23.5" + "@eslint/config-helpers" "^0.6.0" + "@eslint/core" "^1.2.1" + "@eslint/plugin-kit" "^0.7.2" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^9.1.2" + eslint-visitor-keys "^5.0.1" + espree "^11.2.0" + esquery "^1.7.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + minimatch "^10.2.4" + natural-compare "^1.4.0" + optionator "^0.9.3" + +"espree@^10.3.0 || ^11.0.0", espree@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5" + integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== + dependencies: + acorn "^8.16.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^5.0.1" + +esquery@^1.6.0, esquery@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -1210,11 +1565,31 @@ estree-walker@^3.0.2, estree-walker@^3.0.3: dependencies: "@types/estree" "^1.0.0" +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + expect-type@^1.2.1: version "1.4.0" resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.4.0.tgz#24edf7f0cc69a44d008567ba4594ab96f3c3a3d6" integrity sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA== +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fast-string-truncated-width@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz#23afe0da67d752ca0727538f1e6967759728ce49" @@ -1239,6 +1614,34 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== + for-each@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" @@ -1264,6 +1667,11 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" @@ -1298,6 +1706,13 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob@^10.4.1, glob@^10.4.2: version "10.5.0" resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" @@ -1310,6 +1725,11 @@ glob@^10.4.1, glob@^10.4.2: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +globals@^17.3.0, globals@^17.7.0: + version "17.7.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.7.0.tgz#553d55090b4dde8209ec2da42580d6e7e7d8b10d" + integrity sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg== + gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -1404,6 +1824,21 @@ iconv-lite@0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.6.tgz#6a57aaef4c90df27ac3590875d29e8f11988c88e" + integrity sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" @@ -1468,11 +1903,23 @@ is-date-object@^1.0.5: call-bound "^1.0.2" has-tostringtag "^1.0.2" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-map@^2.0.2, is-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" @@ -1655,6 +2102,53 @@ jsdom@26.1.0: ws "^8.18.0" xml-name-validator "^5.0.0" +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +jsx-ast-utils-x@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils-x/-/jsx-ast-utils-x-0.1.0.tgz#b0933d66a69e0aa1ae23f74fb87b079ec298652f" + integrity sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + loupe@^3.1.0, loupe@^3.1.4: version "3.2.1" resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76" @@ -1710,7 +2204,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^10.2.2: +minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.5: version "10.2.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== @@ -1773,6 +2267,11 @@ nanoid@^3.3.12: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.15.tgz#36c490fad8c6e86c824c940dfdde999b69ed4316" integrity sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + nopt@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" @@ -1780,6 +2279,13 @@ nopt@^7.2.1: dependencies: abbrev "^2.0.0" +nth-check@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + nwsapi@^2.2.16: version "2.2.24" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.24.tgz#f8927043d4c9b516abdebe804a32c8d1f9484d1f" @@ -1815,11 +2321,37 @@ object.assign@^4.1.4: has-symbols "^1.1.0" object-keys "^1.1.1" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + outvariant@^1.4.0, outvariant@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.3.tgz#221c1bfc093e8fec7075497e7799fdbf43d14873" integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA== +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + package-json-from-dist@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" @@ -1837,6 +2369,11 @@ path-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -1880,6 +2417,14 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== +postcss-selector-parser@^7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz#69dc7a526517572ff6b150e352b36a016017b485" + integrity sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss@^8.4.43, postcss@^8.5.15: version "8.5.16" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.16.tgz#1230ce0b5df354c24c0ea45f99ce5f6a88279d28" @@ -1889,6 +2434,16 @@ postcss@^8.4.43, postcss@^8.5.15: picocolors "^1.1.1" source-map-js "^1.2.1" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.5.tgz#4fec97736e33b9d0b620b48914fe93b530e835ad" + integrity sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg== + pretty-format@^27.0.2: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" @@ -1903,7 +2458,7 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -punycode@^2.3.1: +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -1921,6 +2476,21 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +refa@^0.12.0, refa@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/refa/-/refa-0.12.1.tgz#dac13c4782dc22b6bae6cce81a2b863888ea39c6" + integrity sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g== + dependencies: + "@eslint-community/regexpp" "^4.8.0" + +regexp-ast-analysis@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz#c0e24cb2a90f6eadd4cbaaba129317e29d29c482" + integrity sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A== + dependencies: + "@eslint-community/regexpp" "^4.8.0" + refa "^0.12.1" + regexp.prototype.flags@^1.5.1: version "1.5.4" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" @@ -2003,7 +2573,16 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -semver@^7.5.3: +scslre@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/scslre/-/scslre-0.3.0.tgz#c3211e9bfc5547fc86b1eabaa34ed1a657060155" + integrity sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ== + dependencies: + "@eslint-community/regexpp" "^4.8.0" + refa "^0.12.0" + regexp-ast-analysis "^0.7.0" + +semver@^7.5.3, semver@^7.6.3, semver@^7.7.3, semver@^7.8.5: version "7.8.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== @@ -2130,16 +2709,8 @@ strict-event-emitter@^0.5.1: resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2157,14 +2728,8 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2228,7 +2793,7 @@ tinyexec@^0.3.2: resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== -tinyglobby@^0.2.14: +tinyglobby@^0.2.14, tinyglobby@^0.2.15: version "0.2.17" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631" integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== @@ -2296,6 +2861,18 @@ tr46@^5.1.0: dependencies: punycode "^2.3.1" +ts-api-utils@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" + integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-fest@^5.5.0: version "5.8.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.8.0.tgz#6d517998257c33159db4d4da6f18efa33bd47df3" @@ -2303,6 +2880,21 @@ type-fest@^5.5.0: dependencies: tagged-tag "^1.0.0" +typescript-eslint@^8.64.0: + version "8.64.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.64.0.tgz#4984dae4de9dc8bf892acf5c394d0a2a5f08c3e1" + integrity sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ== + dependencies: + "@typescript-eslint/eslint-plugin" "8.64.0" + "@typescript-eslint/parser" "8.64.0" + "@typescript-eslint/typescript-estree" "8.64.0" + "@typescript-eslint/utils" "8.64.0" + +"typescript@>=5 <6.1.0": + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + typescript@^5.0.4: version "5.9.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" @@ -2318,6 +2910,18 @@ until-async@^3.0.2: resolved "https://registry.yarnpkg.com/until-async/-/until-async-3.0.2.tgz#447f1531fdd7bb2b4c7a98869bdb1a4c2a23865f" integrity sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw== +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + vite-node@3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.2.4.tgz#f3676d94c4af1e76898c162c92728bca65f7bb07" @@ -2379,6 +2983,18 @@ vue-component-type-helpers@^3.0.0: resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-3.3.7.tgz#98f2a53901c3883a674bbc063f74c51a97eb7057" integrity sha512-Skkhw9agYSgsWqv7bxSOGJZa9SaiJbZVGdXuFWnrzKaQYHnw9qbjD630rw6RyMqDbp54nfLCLw5SZA55if7JLg== +vue-eslint-parser@^10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz#3d0aabb6604dac3fcd94f893291cb9e754cc392c" + integrity sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA== + dependencies: + debug "^4.4.0" + eslint-scope "^8.2.0 || ^9.0.0" + eslint-visitor-keys "^4.2.0 || ^5.0.0" + espree "^10.3.0 || ^11.0.0" + esquery "^1.6.0" + semver "^7.6.3" + vue-tsc@^2.0.10: version "2.2.12" resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.2.12.tgz#5f719b08ef7390a763c1a20169ca5c9d09d55688" @@ -2484,16 +3100,13 @@ why-is-node-running@^2.3.0: siginfo "^2.0.0" stackback "0.0.2" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -2531,6 +3144,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yaml@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -2548,3 +3166,8 @@ yargs@^17.7.2: string-width "^4.2.3" y18n "^5.0.5" yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/tests/v2/subscribeassistantenhanced/frontend/src/components/__tests__/Config.spec.ts b/tests/v2/subscribeassistantenhanced/frontend/src/components/__tests__/Config.spec.ts index 59082ab0..c54f482e 100644 --- a/tests/v2/subscribeassistantenhanced/frontend/src/components/__tests__/Config.spec.ts +++ b/tests/v2/subscribeassistantenhanced/frontend/src/components/__tests__/Config.spec.ts @@ -86,10 +86,12 @@ describe('SubscribeAssistantEnhanced config', () => { await user.click(headerButton('sae-config-header__run')) expect(events.save).toHaveBeenCalledOnce() - expect(events.save).toHaveBeenCalledWith(expect.objectContaining({ - onlyonce: true, - reset_task: false, - })) + expect(events.save).toHaveBeenCalledWith( + expect.objectContaining({ + onlyonce: true, + reset_task: false, + }), + ) expect(events.close).not.toHaveBeenCalled() }) @@ -115,12 +117,9 @@ describe('SubscribeAssistantEnhanced config', () => { await waitFor(() => { expect(screen.getByRole('heading', { name: group.title })).toBeInTheDocument() }) - for (const field of localizedFields.filter(item => ( - item.group === group.key - && !item.legacyUiKey - && !item.dialogOnly - && item.kind !== 'textarea' - ))) { + for (const field of localizedFields.filter( + item => item.group === group.key && !item.legacyUiKey && !item.dialogOnly && item.kind !== 'textarea', + )) { expect( screen.queryAllByLabelText(field.label, { exact: true }).length, `${group.key}/${field.key}`, @@ -135,11 +134,17 @@ describe('SubscribeAssistantEnhanced config', () => { await user.click(within(navigation).getByText('订阅清理')) await user.click(screen.getByRole('button', { name: '编辑Tracker响应关键字' })) - const tracker = await screen.findByRole('textbox', { name: 'Tracker响应关键字' }) + const tracker = await screen.findByRole('textbox', { + name: 'Tracker响应关键字', + }) const trackerDialog = tracker.closest('[role="dialog"]') as HTMLElement await user.clear(tracker) await user.type(tracker, 'tracker failure') - await user.click(within(trackerDialog).getByRole('button', { name: '关闭 Tracker响应关键字' })) + await user.click( + within(trackerDialog).getByRole('button', { + name: '关闭 Tracker响应关键字', + }), + ) await user.click(within(navigation).getByText('识别增强')) await user.click(screen.getByRole('button', { name: '编辑自定义识别规则' })) @@ -150,10 +155,12 @@ describe('SubscribeAssistantEnhanced config', () => { await user.click(within(yamlDialog).getByRole('button', { name: '关闭' })) await user.click(headerButton('sae-config-header__save')) - expect(events.save).toHaveBeenCalledWith(expect.objectContaining({ - default_tracker_response: 'tracker failure', - recognition_guard_custom_config: 'rules: enabled', - })) + expect(events.save).toHaveBeenCalledWith( + expect.objectContaining({ + default_tracker_response: 'tracker failure', + recognition_guard_custom_config: 'rules: enabled', + }), + ) }) it('updates number and text controls while keeping multi-select summaries compact', async () => { @@ -165,7 +172,9 @@ describe('SubscribeAssistantEnhanced config', () => { const navigation = document.querySelector('.sae-group-nav__list') as HTMLElement await user.click(within(navigation).getByText('订阅清理')) - const timeout = screen.getByRole('spinbutton', { name: '下载超时时间(分钟)' }) + const timeout = screen.getByRole('spinbutton', { + name: '下载超时时间(分钟)', + }) await user.click(screen.getByRole('button', { name: '减小下载超时时间(分钟)' })) expect(timeout).toHaveValue(119) await user.click(screen.getByRole('button', { name: '增大下载超时时间(分钟)' })) @@ -200,7 +209,11 @@ describe('SubscribeAssistantEnhanced config', () => { const header = document.querySelector('.sae-config-header') expect(header).not.toBeNull() expect(within(header as HTMLElement).getByRole('button', { name: 'Run once' })).toBeInTheDocument() - expect(within(header as HTMLElement).getByRole('button', { name: 'Save changes' })).toBeInTheDocument() + expect( + within(header as HTMLElement).getByRole('button', { + name: 'Save changes', + }), + ).toBeInTheDocument() expect(within(header as HTMLElement).getAllByRole('button', { name: 'Close' })).toHaveLength(2) }) @@ -224,25 +237,27 @@ describe('SubscribeAssistantEnhanced config', () => { const disconnect = vi.fn() const observerCallbacks: IntersectionObserverCallback[] = [] const removeEventListener = vi.spyOn(HTMLElement.prototype, 'removeEventListener') - vi.stubGlobal('IntersectionObserver', class { - constructor(callback: IntersectionObserverCallback) { - observerCallbacks.push(callback) - } - disconnect = disconnect - observe() {} - takeRecords() { return [] } - unobserve() {} - }) + vi.stubGlobal( + 'IntersectionObserver', + class { + constructor(callback: IntersectionObserverCallback) { + observerCallbacks.push(callback) + } + disconnect = disconnect + observe() {} + takeRecords() { + return [] + } + unobserve() {} + }, + ) const { unmount } = await renderConfig() const fieldSurface = document.querySelector('.sae-field-surface') as HTMLElement const header = document.querySelector('.sae-config-header') as HTMLElement await fireEvent.scroll(fieldSurface) expect(fieldSurface).toHaveClass('sae-config-scroll-root--active') - observerCallbacks.at(-1)?.( - [{ isIntersecting: false } as IntersectionObserverEntry], - {} as IntersectionObserver, - ) + observerCallbacks.at(-1)?.([{ isIntersecting: false } as IntersectionObserverEntry], {} as IntersectionObserver) await waitFor(() => expect(header).toHaveClass('sae-config-header--scrolled')) unmount() diff --git a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/api.spec.ts b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/api.spec.ts index 0ddef944..55d7f23d 100644 --- a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/api.spec.ts +++ b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/api.spec.ts @@ -1,10 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import { - loadSummary, - type PluginApi, - type SummaryPayload, -} from '@/config/api' +import { loadSummary, type PluginApi, type SummaryPayload } from '@/config/api' describe('summary API helper', () => { it('returns null without requesting when the host API is omitted', async () => { diff --git a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/draft.spec.ts b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/draft.spec.ts index 2978946f..bb57198b 100644 --- a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/draft.spec.ts +++ b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/draft.spec.ts @@ -54,9 +54,8 @@ describe('configuration draft contract', () => { if (field.kind === 'multi-select') { const candidates = optionValues?.map(String) ?? [] - draftValues[field.key] = JSON.stringify(current) === JSON.stringify([candidates[0]]) - ? [candidates[1]] - : [candidates[0]] + draftValues[field.key] = + JSON.stringify(current) === JSON.stringify([candidates[0]]) ? [candidates[1]] : [candidates[0]] } else if (optionValues?.length) { draftValues[field.key] = optionValues.find(value => value !== current) } else if (typeof current === 'boolean') { diff --git a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/i18n.spec.ts b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/i18n.spec.ts index 9436db16..ff3a7527 100644 --- a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/i18n.spec.ts +++ b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/i18n.spec.ts @@ -1,13 +1,7 @@ import { describe, expect, it } from 'vitest' import { fields, groups } from '@/config/fields' -import { - assertTranslationCoverage, - localizeFields, - localizeGroups, - normalizeLocale, - t, -} from '@/config/i18n' +import { assertTranslationCoverage, localizeFields, localizeGroups, normalizeLocale, t } from '@/config/i18n' describe('SAE i18n adapter', () => { it('normalizes Host locale values and ref-like values', () => { @@ -56,9 +50,7 @@ describe('SAE i18n adapter', () => { expect(localized).toHaveLength(fields.length) expect(localized.every(field => field.label.trim())).toBe(true) expect(localized.filter(field => field.hint).length).toBe(fields.filter(field => field.hint).length) - expect( - localized.every(field => field.options?.every(option => option.title.trim()) ?? true), - ).toBe(true) + expect(localized.every(field => field.options?.every(option => option.title.trim()) ?? true)).toBe(true) } const traditional = localizeFields('zh-TW', fields) @@ -70,17 +62,20 @@ describe('SAE i18n adapter', () => { expect(traditionalText).not.toMatch(/[后会将处为与发过这则无设选线响应种从开进间数长现还较达实复对内样并当]/) expect(traditional.find(field => field.key === 'enabled')?.label).toBe('啟用外掛') expect(english.find(field => field.key === 'enabled')?.label).toBe('Enable plugin') - expect( - english.find(field => field.key === 'completion_guard_mode')?.options?.map(option => option.title), - ).toEqual(['Off', 'Strict', 'Balanced', 'Relaxed']) - expect( - localizeFields('zh-CN', fields).find(field => field.key === 'recognition_guard_custom_config')?.hint, - ).toBe('仅在内置规则无法满足时编辑,留空则继承当前模式') - expect( - traditional.find(field => field.key === 'recognition_guard_custom_config')?.hint, - ).toBe('僅在內建規則無法滿足時編輯,留空則繼承目前模式') - expect( - english.find(field => field.key === 'recognition_guard_custom_config')?.hint, - ).toBe('Edit only when built-in rules are insufficient; leave empty to inherit the current mode') + expect(english.find(field => field.key === 'completion_guard_mode')?.options?.map(option => option.title)).toEqual([ + 'Off', + 'Strict', + 'Balanced', + 'Relaxed', + ]) + expect(localizeFields('zh-CN', fields).find(field => field.key === 'recognition_guard_custom_config')?.hint).toBe( + '仅在内置规则无法满足时编辑,留空则继承当前模式', + ) + expect(traditional.find(field => field.key === 'recognition_guard_custom_config')?.hint).toBe( + '僅在內建規則無法滿足時編輯,留空則繼承目前模式', + ) + expect(english.find(field => field.key === 'recognition_guard_custom_config')?.hint).toBe( + 'Edit only when built-in rules are insufficient; leave empty to inherit the current mode', + ) }) }) diff --git a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/presentation.spec.ts b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/presentation.spec.ts index b3e67444..b7a851a0 100644 --- a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/presentation.spec.ts +++ b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/presentation.spec.ts @@ -1,10 +1,7 @@ import { describe, expect, it } from 'vitest' import { fields } from '@/config/fields' -import { - displayFieldLabel, - numberFieldUnit, -} from '@/config/presentation' +import { displayFieldLabel, numberFieldUnit } from '@/config/presentation' describe('number field presentation', () => { it('gives every number stepper an explicit unit', () => { @@ -22,7 +19,9 @@ describe('number field presentation', () => { const numberFields = fields.filter(field => field.kind === 'number') for (const field of numberFields) { - expect(displayFieldLabel(field)).not.toMatch(/[((][^))]+[))]/) + const label = displayFieldLabel(field) + expect(label).not.toContain('(') + expect(label).not.toContain('(') } }) diff --git a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/values.spec.ts b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/values.spec.ts index 05289f1d..3ea43b8e 100644 --- a/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/values.spec.ts +++ b/tests/v2/subscribeassistantenhanced/frontend/src/config/__tests__/values.spec.ts @@ -1,10 +1,7 @@ import { describe, expect, it } from 'vitest' import { configDefaults } from '@/config/defaults' -import { - normalizeFiniteNumber, - normalizeSaeConfig, -} from '@/config/values' +import { normalizeFiniteNumber, normalizeSaeConfig } from '@/config/values' describe('normalizeFiniteNumber', () => { it('将整数字符串转换为 number', () => { @@ -65,15 +62,11 @@ describe('normalizeSaeConfig', () => { expect(result.onlyonce).toBe(true) expect(result.reset_task).toBe(true) expect(result.auto_check_interval_minutes).toBe(45) - expect(result.download_check_interval_minutes).toBe( - configDefaults.download_check_interval_minutes, - ) + expect(result.download_check_interval_minutes).toBe(configDefaults.download_check_interval_minutes) expect(result.meta_check_interval_hours).toBe(configDefaults.meta_check_interval_hours) expect(result.download_timeout_minutes).toBe(configDefaults.download_timeout_minutes) expect(result.cadence_multiplier).toBe(3.75) - expect(result.best_version_movie_remaining_days).toBe( - configDefaults.best_version_movie_remaining_days, - ) + expect(result.best_version_movie_remaining_days).toBe(configDefaults.best_version_movie_remaining_days) expect(result.subscription_cleanup_history_scenes).toEqual(['normal', 'best_version']) expect(result.no_download_actions).toEqual(['pause_movie', 'delete_tv']) expect(result.paused_probe_reasons).toEqual(['no_download', 'pre_air']) @@ -91,12 +84,8 @@ describe('normalizeSaeConfig', () => { it('缺失列表字段使用独立的默认数组', () => { const result = normalizeSaeConfig({}) - expect(result.subscription_cleanup_history_scenes).toEqual( - configDefaults.subscription_cleanup_history_scenes, - ) - expect(result.subscription_cleanup_history_scenes).not.toBe( - configDefaults.subscription_cleanup_history_scenes, - ) + expect(result.subscription_cleanup_history_scenes).toEqual(configDefaults.subscription_cleanup_history_scenes) + expect(result.subscription_cleanup_history_scenes).not.toBe(configDefaults.subscription_cleanup_history_scenes) expect(result.no_download_actions).not.toBe(configDefaults.no_download_actions) expect(result.paused_probe_reasons).not.toBe(configDefaults.paused_probe_reasons) }) diff --git a/tests/v2/subscribeassistantenhanced/frontend/support/render.ts b/tests/v2/subscribeassistantenhanced/frontend/support/render.ts index fc0a4595..d5c95e52 100644 --- a/tests/v2/subscribeassistantenhanced/frontend/support/render.ts +++ b/tests/v2/subscribeassistantenhanced/frontend/support/render.ts @@ -25,11 +25,12 @@ const CronFieldStub = defineComponent({ }, emits: ['update:modelValue'], setup(props, { attrs, emit }) { - return () => h('input', { - ...attrs, - value: props.modelValue, - onInput: (event: Event) => emit('update:modelValue', (event.target as HTMLInputElement).value), - }) + return () => + h('input', { + ...attrs, + value: props.modelValue, + onInput: (event: Event) => emit('update:modelValue', (event.target as HTMLInputElement).value), + }) }, }) @@ -41,22 +42,19 @@ const AceEditorStub = defineComponent({ }, emits: ['update:value'], setup(props, { attrs, emit }) { - return () => h('textarea', { - ...attrs, - 'aria-label': 'YAML', - value: props.value, - onInput: (event: Event) => emit('update:value', (event.target as HTMLTextAreaElement).value), - }) + return () => + h('textarea', { + ...attrs, + 'aria-label': 'YAML', + value: props.value, + onInput: (event: Event) => emit('update:value', (event.target as HTMLTextAreaElement).value), + }) }, }) /** 使用真实 Vuetify 与最小 Host 契约渲染联邦配置组件。 */ export function renderWithHost(component: Component, options: RenderWithHostOptions = {}) { - const { - global: globalOptions, - locale = 'zh-CN', - ...renderOptions - } = options + const { global: globalOptions, locale = 'zh-CN', ...renderOptions } = options const localeRef = ref(locale) const vuetify = createVuetify({ components, directives }) // Vuetify 在插件安装阶段注入自身全局属性,挂载配置只需补充 Host 语言契约。