From 2898e6455c67c6dd31ef754598c68273b03f8718 Mon Sep 17 00:00:00 2001 From: AdingApkgg Date: Thu, 29 Jan 2026 03:03:28 +0800 Subject: [PATCH] feat: enhance SettingsModal with search history management and project information - Added a new section in SettingsModal for managing search history, including export and import functionality. - Updated the project information card to provide links to the project's GitHub repositories. - Removed the previous search history display and buttons, streamlining the settings interface. - Integrated GitHub hash generation for dynamic repository links. --- package.json | 20 +- pnpm-lock.yaml | 375 +++++++++++++----------- src/components/SettingsModal.vue | 204 ++++++++----- src/composables/useBackgroundImage.ts | 393 +++++++------------------- src/data/api.json | 4 +- src/data/repository-opengraph.json | 6 + src/utils/imageDB.ts | 297 ------------------- 7 files changed, 472 insertions(+), 827 deletions(-) create mode 100644 src/data/repository-opengraph.json delete mode 100644 src/utils/imageDB.ts diff --git a/package.json b/package.json index 696f8fa..c2d226b 100644 --- a/package.json +++ b/package.json @@ -13,29 +13,29 @@ "devDependencies": { "@eslint/js": "^9.39.2", "@tailwindcss/vite": "^4.1.18", - "@types/node": "^25.0.3", - "@typescript-eslint/eslint-plugin": "^8.52.0", - "@typescript-eslint/parser": "^8.52.0", + "@types/node": "^25.1.0", + "@typescript-eslint/eslint-plugin": "^8.54.0", + "@typescript-eslint/parser": "^8.54.0", "@vitejs/plugin-vue": "^6.0.3", "eslint": "^9.39.2", - "eslint-plugin-vue": "^10.6.2", + "eslint-plugin-vue": "^10.7.0", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", - "typescript-eslint": "^8.52.0", + "typescript-eslint": "^8.54.0", "vite": "^7.3.1", "vite-plugin-pwa": "^1.2.0", - "vue-tsc": "^3.2.2", + "vue-tsc": "^3.2.4", "workbox-window": "^7.4.0" }, "dependencies": { - "@fontsource/noto-sans-sc": "^5.2.8", + "@fontsource/noto-sans-sc": "^5.2.9", "@tanstack/vue-virtual": "^3.13.18", "artalk": "^2.9.1", - "lucide-vue-next": "^0.562.0", + "lucide-vue-next": "^0.563.0", "pinia": "^3.0.4", "prismjs": "^1.30.0", - "vue": "^3.5.26", + "vue": "^3.5.27", "vue-prism-editor": "2.0.0-alpha.2" }, - "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" + "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac11894..9c564f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,54 +9,54 @@ importers: .: dependencies: '@fontsource/noto-sans-sc': - specifier: ^5.2.8 - version: 5.2.8 + specifier: ^5.2.9 + version: 5.2.9 '@tanstack/vue-virtual': specifier: ^3.13.18 - version: 3.13.18(vue@3.5.26(typescript@5.9.3)) + version: 3.13.18(vue@3.5.27(typescript@5.9.3)) artalk: specifier: ^2.9.1 version: 2.9.1(marked@14.1.4) lucide-vue-next: - specifier: ^0.562.0 - version: 0.562.0(vue@3.5.26(typescript@5.9.3)) + specifier: ^0.563.0 + version: 0.563.0(vue@3.5.27(typescript@5.9.3)) pinia: specifier: ^3.0.4 - version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) + version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) prismjs: specifier: ^1.30.0 version: 1.30.0 vue: - specifier: ^3.5.26 - version: 3.5.26(typescript@5.9.3) + specifier: ^3.5.27 + version: 3.5.27(typescript@5.9.3) vue-prism-editor: specifier: 2.0.0-alpha.2 - version: 2.0.0-alpha.2(vue@3.5.26(typescript@5.9.3)) + version: 2.0.0-alpha.2(vue@3.5.27(typescript@5.9.3)) devDependencies: '@eslint/js': specifier: ^9.39.2 version: 9.39.2 '@tailwindcss/vite': specifier: ^4.1.18 - version: 4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)) + version: 4.1.18(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)) '@types/node': - specifier: ^25.0.3 - version: 25.0.3 + specifier: ^25.1.0 + version: 25.1.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.52.0 - version: 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.54.0 + version: 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.52.0 - version: 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.54.0 + version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@vitejs/plugin-vue': specifier: ^6.0.3 - version: 6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.26(typescript@5.9.3)) + version: 6.0.3(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.27(typescript@5.9.3)) eslint: specifier: ^9.39.2 version: 9.39.2(jiti@2.6.1) eslint-plugin-vue: - specifier: ^10.6.2 - version: 10.6.2(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))) + specifier: ^10.7.0 + version: 10.7.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -64,17 +64,17 @@ importers: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.52.0 - version: 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.54.0 + version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) + version: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0) + version: 1.2.0(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0) vue-tsc: - specifier: ^3.2.2 - version: 3.2.2(typescript@5.9.3) + specifier: ^3.2.4 + version: 3.2.4(typescript@5.9.3) workbox-window: specifier: ^7.4.0 version: 7.4.0 @@ -782,8 +782,8 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@fontsource/noto-sans-sc@5.2.8': - resolution: {integrity: sha512-8T8HxIS3uAMCfaQawKRH/6yYZ1oRnJZB/CrGwfxGgJa+zAOBgx2lqZMiTY/WbQpLGlPRqX4zHXJYI09CI2q6tA==} + '@fontsource/noto-sans-sc@5.2.9': + resolution: {integrity: sha512-bTUIWGBgJDpwi5qAr+x0/lcgv80IHTB9vl6s2f6EymZEa7qYV99yNRBZuKFT+SYDKVunZrjCEhWtpxqmbXWl5Q==} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -918,56 +918,67 @@ packages: resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.2': resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.2': resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.2': resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.2': resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.2': resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.2': resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.2': resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.2': resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.2': resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.2': resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.2': resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} @@ -1035,24 +1046,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -1104,8 +1119,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@25.0.3': - resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} + '@types/node@25.1.0': + resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1113,63 +1128,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.52.0': - resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==} + '@typescript-eslint/eslint-plugin@8.54.0': + resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.52.0 + '@typescript-eslint/parser': ^8.54.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.52.0': - resolution: {integrity: sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==} + '@typescript-eslint/parser@8.54.0': + resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.52.0': - resolution: {integrity: sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==} + '@typescript-eslint/project-service@8.54.0': + resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.52.0': - resolution: {integrity: sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==} + '@typescript-eslint/scope-manager@8.54.0': + resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.52.0': - resolution: {integrity: sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==} + '@typescript-eslint/tsconfig-utils@8.54.0': + resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.52.0': - resolution: {integrity: sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==} + '@typescript-eslint/type-utils@8.54.0': + resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.52.0': - resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==} + '@typescript-eslint/types@8.54.0': + resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.52.0': - resolution: {integrity: sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==} + '@typescript-eslint/typescript-estree@8.54.0': + resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.52.0': - resolution: {integrity: sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==} + '@typescript-eslint/utils@8.54.0': + resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.52.0': - resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==} + '@typescript-eslint/visitor-keys@8.54.0': + resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-vue@6.0.3': @@ -1191,14 +1206,20 @@ packages: '@vue/compiler-core@3.5.26': resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + '@vue/compiler-core@3.5.27': + resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==} + '@vue/compiler-dom@3.5.26': resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} - '@vue/compiler-sfc@3.5.26': - resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + '@vue/compiler-dom@3.5.27': + resolution: {integrity: sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==} - '@vue/compiler-ssr@3.5.26': - resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + '@vue/compiler-sfc@3.5.27': + resolution: {integrity: sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==} + + '@vue/compiler-ssr@3.5.27': + resolution: {integrity: sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==} '@vue/devtools-api@7.7.8': resolution: {integrity: sha512-BtFcAmDbtXGwurWUFf8ogIbgZyR+rcVES1TSNEI8Em80fD8Anu+qTRN1Fc3J6vdRHlVM3fzPV1qIo+B4AiqGzw==} @@ -1209,26 +1230,29 @@ packages: '@vue/devtools-shared@7.7.8': resolution: {integrity: sha512-XHpO3jC5nOgYr40M9p8Z4mmKfTvUxKyRcUnpBAYg11pE78eaRFBKb0kG5yKLroMuJeeNH9LWmKp2zMU5LUc7CA==} - '@vue/language-core@3.2.2': - resolution: {integrity: sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ==} + '@vue/language-core@3.2.4': + resolution: {integrity: sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==} - '@vue/reactivity@3.5.26': - resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + '@vue/reactivity@3.5.27': + resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==} - '@vue/runtime-core@3.5.26': - resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + '@vue/runtime-core@3.5.27': + resolution: {integrity: sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==} - '@vue/runtime-dom@3.5.26': - resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + '@vue/runtime-dom@3.5.27': + resolution: {integrity: sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==} - '@vue/server-renderer@3.5.26': - resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + '@vue/server-renderer@3.5.27': + resolution: {integrity: sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==} peerDependencies: - vue: 3.5.26 + vue: 3.5.27 '@vue/shared@3.5.26': resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + '@vue/shared@3.5.27': + resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1509,8 +1533,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-vue@10.6.2: - resolution: {integrity: sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==} + eslint-plugin-vue@10.7.0: + resolution: {integrity: sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -1960,24 +1984,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -2018,8 +2046,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-vue-next@0.562.0: - resolution: {integrity: sha512-LN0BLGKMFulv0lnfK29r14DcngRUhIqdcaL0zXTt2o0oS9odlrjCGaU3/X9hIihOjjN8l8e+Y9G/famcNYaI7Q==} + lucide-vue-next@0.563.0: + resolution: {integrity: sha512-zsE/lCKtmaa7bGfhSpN84br1K9YoQ5pCN+2oKWjQQG3Lo6ufUUKBuHSjNFI6RvUevxaajNXb8XwFUKeTXG3sIA==} peerDependencies: vue: '>=3.0.1' @@ -2454,8 +2482,8 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.52.0: - resolution: {integrity: sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==} + typescript-eslint@8.54.0: + resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2580,14 +2608,14 @@ packages: peerDependencies: vue: ^3.0.0 - vue-tsc@3.2.2: - resolution: {integrity: sha512-r9YSia/VgGwmbbfC06hDdAatH634XJ9nVl6Zrnz1iK4ucp8Wu78kawplXnIDa3MSu1XdQQePTHLXYwPDWn+nyQ==} + vue-tsc@3.2.4: + resolution: {integrity: sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.5.26: - resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + vue@3.5.27: + resolution: {integrity: sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -3485,7 +3513,7 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@fontsource/noto-sans-sc@5.2.8': {} + '@fontsource/noto-sans-sc@5.2.9': {} '@humanfs/core@0.19.1': {} @@ -3721,19 +3749,19 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))': + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))': dependencies: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 4.1.18 tailwindcss: 4.1.18 - vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) + vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) '@tanstack/virtual-core@3.13.18': {} - '@tanstack/vue-virtual@3.13.18(vue@3.5.26(typescript@5.9.3))': + '@tanstack/vue-virtual@3.13.18(vue@3.5.27(typescript@5.9.3))': dependencies: '@tanstack/virtual-core': 3.13.18 - vue: 3.5.26(typescript@5.9.3) + vue: 3.5.27(typescript@5.9.3) '@types/estree@0.0.39': {} @@ -3741,7 +3769,7 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@25.0.3': + '@types/node@25.1.0': dependencies: undici-types: 7.16.0 @@ -3749,14 +3777,14 @@ snapshots: '@types/trusted-types@2.0.7': {} - '@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/type-utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.52.0 + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -3765,41 +3793,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.52.0 + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.52.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) - '@typescript-eslint/types': 8.52.0 + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.52.0': + '@typescript-eslint/scope-manager@8.54.0': dependencies: - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/visitor-keys': 8.52.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 - '@typescript-eslint/tsconfig-utils@8.52.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) @@ -3807,14 +3835,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.52.0': {} + '@typescript-eslint/types@8.54.0': {} - '@typescript-eslint/typescript-estree@8.52.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.52.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/visitor-keys': 8.52.0 + '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -3824,27 +3852,27 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.52.0': + '@typescript-eslint/visitor-keys@8.54.0': dependencies: - '@typescript-eslint/types': 8.52.0 + '@typescript-eslint/types': 8.54.0 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-vue@6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.26(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.3(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.27(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.53 - vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) - vue: 3.5.26(typescript@5.9.3) + vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) + vue: 3.5.27(typescript@5.9.3) '@volar/language-core@2.4.27': dependencies: @@ -3866,27 +3894,40 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.5.27': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.27 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.26': dependencies: '@vue/compiler-core': 3.5.26 '@vue/shared': 3.5.26 - '@vue/compiler-sfc@3.5.26': + '@vue/compiler-dom@3.5.27': + dependencies: + '@vue/compiler-core': 3.5.27 + '@vue/shared': 3.5.27 + + '@vue/compiler-sfc@3.5.27': dependencies: '@babel/parser': 7.28.5 - '@vue/compiler-core': 3.5.26 - '@vue/compiler-dom': 3.5.26 - '@vue/compiler-ssr': 3.5.26 - '@vue/shared': 3.5.26 + '@vue/compiler-core': 3.5.27 + '@vue/compiler-dom': 3.5.27 + '@vue/compiler-ssr': 3.5.27 + '@vue/shared': 3.5.27 estree-walker: 2.0.2 magic-string: 0.30.21 postcss: 8.5.6 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.26': + '@vue/compiler-ssr@3.5.27': dependencies: - '@vue/compiler-dom': 3.5.26 - '@vue/shared': 3.5.26 + '@vue/compiler-dom': 3.5.27 + '@vue/shared': 3.5.27 '@vue/devtools-api@7.7.8': dependencies: @@ -3906,7 +3947,7 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/language-core@3.2.2': + '@vue/language-core@3.2.4': dependencies: '@volar/language-core': 2.4.27 '@vue/compiler-dom': 3.5.26 @@ -3916,30 +3957,32 @@ snapshots: path-browserify: 1.0.1 picomatch: 4.0.3 - '@vue/reactivity@3.5.26': + '@vue/reactivity@3.5.27': dependencies: - '@vue/shared': 3.5.26 + '@vue/shared': 3.5.27 - '@vue/runtime-core@3.5.26': + '@vue/runtime-core@3.5.27': dependencies: - '@vue/reactivity': 3.5.26 - '@vue/shared': 3.5.26 + '@vue/reactivity': 3.5.27 + '@vue/shared': 3.5.27 - '@vue/runtime-dom@3.5.26': + '@vue/runtime-dom@3.5.27': dependencies: - '@vue/reactivity': 3.5.26 - '@vue/runtime-core': 3.5.26 - '@vue/shared': 3.5.26 + '@vue/reactivity': 3.5.27 + '@vue/runtime-core': 3.5.27 + '@vue/shared': 3.5.27 csstype: 3.2.3 - '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))': + '@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3))': dependencies: - '@vue/compiler-ssr': 3.5.26 - '@vue/shared': 3.5.26 - vue: 3.5.26(typescript@5.9.3) + '@vue/compiler-ssr': 3.5.27 + '@vue/shared': 3.5.27 + vue: 3.5.27(typescript@5.9.3) '@vue/shared@3.5.26': {} + '@vue/shared@3.5.27': {} + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -4290,9 +4333,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))): + eslint-plugin-vue@10.7.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) eslint: 9.39.2(jiti@2.6.1) natural-compare: 1.4.0 nth-check: 2.1.1 @@ -4301,7 +4344,7 @@ snapshots: vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1)) xml-name-validator: 4.0.0 optionalDependencies: - '@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint-scope@8.4.0: dependencies: @@ -4776,9 +4819,9 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-vue-next@0.562.0(vue@3.5.26(typescript@5.9.3)): + lucide-vue-next@0.563.0(vue@3.5.27(typescript@5.9.3)): dependencies: - vue: 3.5.26(typescript@5.9.3) + vue: 3.5.27(typescript@5.9.3) magic-string@0.25.9: dependencies: @@ -4889,10 +4932,10 @@ snapshots: picomatch@4.0.3: {} - pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)): + pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)): dependencies: '@vue/devtools-api': 7.7.8 - vue: 3.5.26(typescript@5.9.3) + vue: 3.5.27(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -5270,12 +5313,12 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -5323,18 +5366,18 @@ snapshots: util-deprecate@1.0.2: {} - vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0): + vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) + vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) workbox-build: 7.4.0 workbox-window: 7.4.0 transitivePeerDependencies: - supports-color - vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1): + vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -5343,7 +5386,7 @@ snapshots: rollup: 4.53.2 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.1.0 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 @@ -5363,23 +5406,23 @@ snapshots: transitivePeerDependencies: - supports-color - vue-prism-editor@2.0.0-alpha.2(vue@3.5.26(typescript@5.9.3)): + vue-prism-editor@2.0.0-alpha.2(vue@3.5.27(typescript@5.9.3)): dependencies: - vue: 3.5.26(typescript@5.9.3) + vue: 3.5.27(typescript@5.9.3) - vue-tsc@3.2.2(typescript@5.9.3): + vue-tsc@3.2.4(typescript@5.9.3): dependencies: '@volar/typescript': 2.4.27 - '@vue/language-core': 3.2.2 + '@vue/language-core': 3.2.4 typescript: 5.9.3 - vue@3.5.26(typescript@5.9.3): + vue@3.5.27(typescript@5.9.3): dependencies: - '@vue/compiler-dom': 3.5.26 - '@vue/compiler-sfc': 3.5.26 - '@vue/runtime-dom': 3.5.26 - '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3)) - '@vue/shared': 3.5.26 + '@vue/compiler-dom': 3.5.27 + '@vue/compiler-sfc': 3.5.27 + '@vue/runtime-dom': 3.5.27 + '@vue/server-renderer': 3.5.27(vue@3.5.27(typescript@5.9.3)) + '@vue/shared': 3.5.27 optionalDependencies: typescript: 5.9.3 diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue index cc69454..9988b2c 100644 --- a/src/components/SettingsModal.vue +++ b/src/components/SettingsModal.vue @@ -91,6 +91,81 @@ + +
+
+
+ +
+
+

搜索历史

+

+ 共 {{ historyStore.historyCount }} 条记录 +

+
+
+ +
+ +
+ + + + +
+ + + +
+ + {{ importMessage }} +
+
+ + +
+ +

导出为 JSON 格式,可用于备份或迁移到其他设备。导入时会自动去重。

+
+
+
+
- +
-
- +
+
-

搜索历史

-

- 共 {{ historyStore.historyCount }} 条记录 -

+

关于项目

+

开源仓库与贡献

@@ -613,10 +648,53 @@ import { useSettingsStore, DEFAULT_API_CONFIG } from '@/stores/settings' import { useHistoryStore } from '@/stores/history' import type { SearchHistory } from '@/utils/persistence' import apiData from '@/data/api.json' +import repoData from '@/data/repository-opengraph.json' const settingsStore = useSettingsStore() const historyStore = useHistoryStore() +// GitHub 关于项目相关 +const githubHashTimestamp = ref('') + +// 生成 SHA-256 hash +async function sha256(message: string): Promise { + const msgBuffer = new TextEncoder().encode(message) + const hashBuffer = await window.crypto.subtle.digest('SHA-256', msgBuffer) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + return hashArray.map(b => b.toString(16).padStart(2, '0')).join('') +} + +// 获取当前日期的 YYMMDD 格式 +function getDateString(): string { + const now = new Date() + const yy = String(now.getFullYear()).slice(-2) + const mm = String(now.getMonth() + 1).padStart(2, '0') + const dd = String(now.getDate()).padStart(2, '0') + return `${yy}${mm}${dd}` +} + +// 初始化 GitHub hash +async function initGitHubHash() { + const dateStr = getDateString() + githubHashTimestamp.value = await sha256(dateStr) +} + +// 从 GitHub URL 提取 owner/repo 路径 +function getRepoPath(url: string): string { + // https://github.com/Moe-Sakura/frontend -> Moe-Sakura/frontend + return url.replace('https://github.com/', '') +} + +// 从 GitHub URL 提取仓库名 +function getRepoName(url: string): string { + // https://github.com/Moe-Sakura/frontend -> frontend + const parts = url.split('/') + return parts[parts.length - 1] +} + +// 组件挂载时初始化 +void initGitHubHash() + // 导入导出状态 const importStatus = ref<'idle' | 'success' | 'error'>('idle') const importMessage = ref('') diff --git a/src/composables/useBackgroundImage.ts b/src/composables/useBackgroundImage.ts index 9dad461..a993ce0 100644 --- a/src/composables/useBackgroundImage.ts +++ b/src/composables/useBackgroundImage.ts @@ -1,11 +1,17 @@ /** * 背景图片管理 composable - * 负责从 API 获取图片、IndexedDB 缓存管理、洗牌队列、Ken Burns 动画等 + * 简单的定时获取随机图片,失败时保持上一张 + * + * 自动参数: + * - name: 跟随 VNDB 获取到的作品名 + * - maxbrightness: 暗色模式自动设为 0.5 + * - view: 移动端自动使用 vertical 模式 */ -import { ref, computed, shallowRef } from 'vue' -import { imageDB } from '@/utils/imageDB' +import { ref, computed } from 'vue' import { useSettingsStore, DEFAULT_API_CONFIG } from '@/stores/settings' +import { useSearchStore } from '@/stores/search' +import { useUIStore } from '@/stores/ui' // Ken Burns 动画变体类型 export type KenBurnsType = @@ -18,14 +24,8 @@ export type KenBurnsType = // 配置常量 const CONFIG = { - MAX_CACHE_SIZE: 10000, // 最大缓存 10000 张图片 - CLEANUP_BATCH_SIZE: 2000, // 每次清理 2000 张 - FETCH_INTERVAL: 5000, // 5秒获取一次(未达到缓存阈值时) - FETCH_INTERVAL_SLOW: 30000, // 30秒获取一次(图片缓存充足时) - CACHE_THRESHOLD: 30, // 缓存阈值,达到后切换到慢速获取 - DISPLAY_INTERVAL: 10000, // 10秒切换一次 - MAX_BLOB_URLS: 20, // 最大同时保持的 Blob URL 数量 - PRELOAD_COUNT: 10, // 预加载图片数量 + DISPLAY_INTERVAL: 30000, // 30秒切换一次 + MOBILE_BREAKPOINT: 768, // 移动端断点 } as const // Ken Burns 效果列表 @@ -38,32 +38,27 @@ const KEN_BURNS_EFFECTS: KenBurnsType[] = [ 'kb-pan-down', ] +// 检测是否为移动端 +function isMobile(): boolean { + return window.innerWidth < CONFIG.MOBILE_BREAKPOINT +} + export function useBackgroundImage() { const settingsStore = useSettingsStore() + const searchStore = useSearchStore() + const uiStore = useUIStore() // 状态 const currentImageUrl = ref('') - const imageCache = shallowRef([]) - const imageCacheSet = shallowRef>(new Set()) - const imageBlobUrls = shallowRef>(new Map()) - const shuffledQueue = shallowRef([]) const currentBgKey = ref(0) const currentKenBurns = ref('kb-zoom-in') // 定时器 - let fetchInterval: number | null = null let displayInterval: number | null = null // 计算属性 const hasBackgroundImage = computed(() => !!currentImageUrl.value) - - const backgroundImageUrl = computed(() => { - if (currentImageUrl.value) { - return imageBlobUrls.value.get(currentImageUrl.value) || currentImageUrl.value - } - return '' - }) - + const backgroundImageUrl = computed(() => currentImageUrl.value) const kenBurnsClass = computed(() => currentKenBurns.value) // 随机选择 Ken Burns 效果 @@ -71,302 +66,123 @@ export function useBackgroundImage() { currentKenBurns.value = KEN_BURNS_EFFECTS[Math.floor(Math.random() * KEN_BURNS_EFFECTS.length)] } - // Fisher-Yates 洗牌算法 - function shuffleArray(array: T[]): T[] { - const shuffled = [...array] - for (let i = shuffled.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]] - } - return shuffled + // 参数级别配置 + interface UrlOptions { + includeName?: boolean + includeBrightness?: boolean + includeView?: boolean } - // 重新洗牌队列 - function reshuffleQueue() { - if (imageCache.value.length > 0) { - shuffledQueue.value = shuffleArray([...imageCache.value]) + // 构建 API URL(自动参数) + function buildApiUrl(options: UrlOptions = {}): string { + const { includeName = true, includeBrightness = true, includeView = true } = options + const baseUrl = settingsStore.settings.backgroundImageApiUrl || DEFAULT_API_CONFIG.backgroundImageApiUrl + const params = new URLSearchParams() + + // 添加时间戳防止缓存 + params.set('t', Date.now().toString()) + + // 来源作品 - 跟随用户输入框内容 + if (includeName) { + const searchQuery = searchStore.searchQuery?.trim() + if (searchQuery) { + params.set('name', searchQuery) + } } + + // 暗色模式 - 自动添加最大亮度 0.5 + if (includeBrightness && uiStore.isDarkMode) { + params.set('maxbrightness', '0.5') + } + + // 移动端 - 使用 vertical 视图模式 + if (includeView && isMobile()) { + params.set('view', 'vertical') + } + + return `${baseUrl}?${params.toString()}` } - // 从 IndexedDB 加载缓存 - async function loadCacheFromDB(): Promise { - try { - await imageDB.init() - const urls = await imageDB.getAllUrls() - - if (urls.length > 0) { - const uniqueUrls = [...new Set(urls)] - imageCache.value = uniqueUrls - imageCacheSet.value = new Set(uniqueUrls) + // 检查是否有来源作品参数 + function hasNameParam(): boolean { + return !!searchStore.searchQuery?.trim() + } + + // 加载图片 + function loadImage(apiUrl: string, onSuccess: () => void, onError: () => void) { + const img = new Image() + img.onload = onSuccess + img.onerror = onError + img.src = apiUrl + } + + // 更新背景图显示 + function updateBackground(apiUrl: string) { + selectRandomKenBurns() + currentImageUrl.value = apiUrl + currentBgKey.value++ + } + + // 获取并显示下一张图片(三级重试) + function fetchNextImage() { + // 第一级:所有参数 + const url1 = buildApiUrl({ includeName: true, includeBrightness: true, includeView: true }) + + loadImage(url1, () => updateBackground(url1), () => { + // 第二级:只保留 name,去掉亮度和视图模式 + if (hasNameParam()) { + const url2 = buildApiUrl({ includeName: true, includeBrightness: false, includeView: false }) - // 预加载部分图片的 Blob URL - const preloadCount = Math.min(CONFIG.PRELOAD_COUNT, uniqueUrls.length) - const newBlobUrls = new Map(imageBlobUrls.value) + loadImage(url2, () => updateBackground(url2), () => { + // 第三级:去掉 name(只保留时间戳) + const url3 = buildApiUrl({ includeName: false, includeBrightness: false, includeView: false }) + + loadImage(url3, () => updateBackground(url3), () => { + // 全部失败,保持当前图片不变 + }) + }) + } else { + // 没有 name 参数,直接尝试基础 URL + const url3 = buildApiUrl({ includeName: false, includeBrightness: false, includeView: false }) - for (let i = 0; i < preloadCount; i++) { - const url = uniqueUrls[i] - const blob = await imageDB.getImageByUrl(url) - if (blob) { - const blobUrl = URL.createObjectURL(blob) - newBlobUrls.set(url, blobUrl) - } - } - imageBlobUrls.value = newBlobUrls - - return true - } - } catch { - // 静默处理错误 - } - return false - } - - // 从 API 获取图片并添加到缓存 - async function fetchAndCacheImage() { - try { - const timestamp = Date.now() - const baseUrl = settingsStore.settings.backgroundImageApiUrl || DEFAULT_API_CONFIG.backgroundImageApiUrl - const apiUrl = `${baseUrl}?t=${timestamp}` - - const response = await fetch(apiUrl) - if (!response.ok) {return} - - const finalUrl = response.url - - // 检查是否已存在 - if (imageCacheSet.value.has(finalUrl)) {return} - - const existsInDB = await imageDB.hasUrl(finalUrl) - if (existsInDB) {return} - - const blob = await response.blob() - - // 验证是否为有效图片 - const img = new Image() - const blobUrl = URL.createObjectURL(blob) - - img.onload = async () => { - if (!imageCacheSet.value.has(finalUrl)) { - try { - await imageDB.addImage(finalUrl, blob) - - // 更新缓存 - const newCache = [...imageCache.value, finalUrl] - const newCacheSet = new Set(imageCacheSet.value) - newCacheSet.add(finalUrl) - const newBlobUrls = new Map(imageBlobUrls.value) - newBlobUrls.set(finalUrl, blobUrl) - - imageCache.value = newCache - imageCacheSet.value = newCacheSet - imageBlobUrls.value = newBlobUrls - - // 限制缓存大小 - await cleanupCacheIfNeeded() - - // 如果刚好达到阈值,重启获取定时器以切换到慢速模式 - if (newCache.length === CONFIG.CACHE_THRESHOLD) { - startFetchInterval() - } - - // 如果队列为空,重新洗牌 - if (shuffledQueue.value.length === 0) { - reshuffleQueue() - } - } catch { - URL.revokeObjectURL(blobUrl) - } - } else { - URL.revokeObjectURL(blobUrl) - } - } - - img.onerror = () => { - URL.revokeObjectURL(blobUrl) - } - - img.src = blobUrl - } catch { - // 静默处理错误 - } - } - - // 清理缓存(如果超过最大大小) - async function cleanupCacheIfNeeded() { - const count = await imageDB.getCount() - if (count <= CONFIG.MAX_CACHE_SIZE) {return} - - const deletedCount = await imageDB.deleteOldestBatch(CONFIG.CLEANUP_BATCH_SIZE) - - // 同步更新内存缓存 - const cleanedCache = imageCache.value.slice(deletedCount) - const cleanedSet = new Set(cleanedCache) - const cleanedBlobUrls = new Map() - - // 清理被删除图片的 Blob URL - for (let i = 0; i < deletedCount; i++) { - const removed = imageCache.value[i] - if (removed) { - const oldBlobUrl = imageBlobUrls.value.get(removed) - if (oldBlobUrl) { - URL.revokeObjectURL(oldBlobUrl) - } - } - } - - // 保留剩余的 Blob URL - imageBlobUrls.value.forEach((url, key) => { - if (cleanedSet.has(key)) { - cleanedBlobUrls.set(key, url) + loadImage(url3, () => updateBackground(url3), () => { + // 失败,保持当前图片不变 + }) } }) - - imageCache.value = cleanedCache - imageCacheSet.value = cleanedSet - imageBlobUrls.value = cleanedBlobUrls } - // 清理过多的 Blob URL 以释放内存 - function cleanupBlobUrls(currentUrl: string) { - const blobUrls = imageBlobUrls.value - if (blobUrls.size <= CONFIG.MAX_BLOB_URLS) {return} - - const newBlobUrls = new Map() - const currentBlobUrl = blobUrls.get(currentUrl) - if (currentBlobUrl) { - newBlobUrls.set(currentUrl, currentBlobUrl) - } - - const entries = Array.from(blobUrls.entries()) - const toKeep = entries.slice(-CONFIG.MAX_BLOB_URLS + 1) - - for (const [url, blobUrl] of entries) { - if (url !== currentUrl && !toKeep.some(([u]) => u === url)) { - URL.revokeObjectURL(blobUrl) - } else if (url !== currentUrl) { - newBlobUrls.set(url, blobUrl) - } - } - - imageBlobUrls.value = newBlobUrls - } - - // 显示下一张图片 - async function displayNextImage() { - // 如果队列为空,重新洗牌 - if (shuffledQueue.value.length === 0) { - if (imageCache.value.length === 0) {return} - reshuffleQueue() - } - - const queue = [...shuffledQueue.value] - const nextImageUrl = queue.shift() - shuffledQueue.value = queue - - if (!nextImageUrl) {return} - - try { - // 检查是否已有 Blob URL - let blobUrl = imageBlobUrls.value.get(nextImageUrl) - - // 如果没有,从 IndexedDB 加载 - if (!blobUrl) { - const blob = await imageDB.getImageByUrl(nextImageUrl) - if (blob) { - blobUrl = URL.createObjectURL(blob) - const newBlobUrls = new Map(imageBlobUrls.value) - newBlobUrls.set(nextImageUrl, blobUrl) - imageBlobUrls.value = newBlobUrls - } - } - - // 预加载图片 - const preloadImg = new Image() - preloadImg.onload = () => { - selectRandomKenBurns() - currentImageUrl.value = nextImageUrl - currentBgKey.value++ - cleanupBlobUrls(nextImageUrl) - } - preloadImg.onerror = () => { - void displayNextImage() - } - preloadImg.src = blobUrl || nextImageUrl - } catch { - void displayNextImage() - } - } - - // 启动图片获取定时器 - function startFetchInterval() { - if (fetchInterval) { - clearInterval(fetchInterval) - } - - void fetchAndCacheImage() - - // 根据缓存数量决定获取间隔 - const interval = imageCache.value.length >= CONFIG.CACHE_THRESHOLD - ? CONFIG.FETCH_INTERVAL_SLOW - : CONFIG.FETCH_INTERVAL - - fetchInterval = window.setInterval(() => { - void fetchAndCacheImage() - }, interval) - } - - // 启动图片显示定时器 - function startDisplayInterval() { + // 启动定时器 + function startInterval() { if (displayInterval) { clearInterval(displayInterval) } - void displayNextImage() + // 立即获取第一张 + fetchNextImage() + // 定时获取下一张 displayInterval = window.setInterval(() => { - void displayNextImage() + fetchNextImage() }, CONFIG.DISPLAY_INTERVAL) } - // 停止所有定时器 - function stopAllIntervals() { - if (fetchInterval) { - clearInterval(fetchInterval) - fetchInterval = null - } + // 停止定时器 + function stopInterval() { if (displayInterval) { clearInterval(displayInterval) displayInterval = null } } - // 清理所有 Blob URL - function cleanupAllBlobUrls() { - imageBlobUrls.value.forEach(blobUrl => { - URL.revokeObjectURL(blobUrl) - }) - imageBlobUrls.value = new Map() - } - // 初始化 async function init() { - await imageDB.init() - - const hasCachedImages = await loadCacheFromDB() - - if (hasCachedImages) { - reshuffleQueue() - } - - startFetchInterval() - startDisplayInterval() + startInterval() } // 销毁 function destroy() { - stopAllIntervals() - cleanupAllBlobUrls() - imageDB.close() + stopInterval() } return { @@ -384,4 +200,3 @@ export function useBackgroundImage() { destroy, } } - diff --git a/src/data/api.json b/src/data/api.json index ba7677f..0c2a043 100644 --- a/src/data/api.json +++ b/src/data/api.json @@ -27,12 +27,12 @@ }, { "key": "us-westapi", - "label": "洛杉矶 ClawCloud", + "label": "圣克拉拉 ClawCloud", "url": "https://us-west.api.searchgal.top" }, { "key": "us-eastapi", - "label": "弗吉尼亚州 ClawCloud", + "label": "阿什本 ClawCloud", "url": "https://us-east.api.searchgal.top" }, { diff --git a/src/data/repository-opengraph.json b/src/data/repository-opengraph.json new file mode 100644 index 0000000..213574d --- /dev/null +++ b/src/data/repository-opengraph.json @@ -0,0 +1,6 @@ +{ + "repositories": [ + "https://github.com/Moe-Sakura/frontend", + "https://github.com/Moe-Sakura/SearchGal" + ] +} \ No newline at end of file diff --git a/src/utils/imageDB.ts b/src/utils/imageDB.ts deleted file mode 100644 index 7812670..0000000 --- a/src/utils/imageDB.ts +++ /dev/null @@ -1,297 +0,0 @@ -/** - * IndexedDB 图片缓存管理 - * 用于存储随机背景图片 - */ - -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -// IndexedDB 事务回调中 result 在 onsuccess 时必定存在 - -const DB_NAME = 'RandomImageCache' -const DB_VERSION = 1 -const STORE_NAME = 'images' - -export interface ImageRecord { - id: number; - url: string; - blob: Blob; - timestamp: number; -} - -class ImageDB { - private db: IDBDatabase | null = null - - /** - * 初始化数据库 - */ - async init(): Promise { - return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION) - - request.onerror = () => { - reject(new Error('无法打开 IndexedDB')) - } - - request.onsuccess = () => { - this.db = request.result - resolve() - } - - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result - - // 创建对象存储 - if (!db.objectStoreNames.contains(STORE_NAME)) { - const objectStore = db.createObjectStore(STORE_NAME, { - keyPath: 'id', - autoIncrement: true, - }) - - // 创建索引 - objectStore.createIndex('url', 'url', { unique: true }) - objectStore.createIndex('timestamp', 'timestamp', { unique: false }) - } - } - }) - } - - /** - * 添加图片到数据库 - */ - async addImage(url: string, blob: Blob): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readwrite') - const objectStore = transaction.objectStore(STORE_NAME) - - const record: Omit = { - url, - blob, - timestamp: Date.now(), - } - - const request = objectStore.add(record) - - request.onsuccess = () => { - resolve(request.result as number) - } - - request.onerror = () => { - reject(new Error('添加图片失败')) - } - }) - } - - /** - * 检查 URL 是否已存在 - */ - async hasUrl(url: string): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readonly') - const objectStore = transaction.objectStore(STORE_NAME) - const index = objectStore.index('url') - - const request = index.getKey(url) - - request.onsuccess = () => { - resolve(request.result !== undefined) - } - - request.onerror = () => { - reject(new Error('检查 URL 失败')) - } - }) - } - - /** - * 获取所有图片 URL - */ - async getAllUrls(): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readonly') - const objectStore = transaction.objectStore(STORE_NAME) - - const request = objectStore.getAll() - - request.onsuccess = () => { - const records = request.result as ImageRecord[] - const urls = records.map(record => record.url) - resolve(urls) - } - - request.onerror = () => { - reject(new Error('获取 URL 列表失败')) - } - }) - } - - /** - * 获取所有图片记录 - */ - async getAllRecords(): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readonly') - const objectStore = transaction.objectStore(STORE_NAME) - - const request = objectStore.getAll() - - request.onsuccess = () => { - resolve(request.result as ImageRecord[]) - } - - request.onerror = () => { - reject(new Error('获取记录失败')) - } - }) - } - - /** - * 根据 URL 获取图片 Blob - */ - async getImageByUrl(url: string): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readonly') - const objectStore = transaction.objectStore(STORE_NAME) - const index = objectStore.index('url') - - const request = index.get(url) - - request.onsuccess = () => { - const record = request.result as ImageRecord | undefined - resolve(record ? record.blob : null) - } - - request.onerror = () => { - reject(new Error('获取图片失败')) - } - }) - } - - /** - * 获取数据库中的图片数量 - */ - async getCount(): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readonly') - const objectStore = transaction.objectStore(STORE_NAME) - - const request = objectStore.count() - - request.onsuccess = () => { - resolve(request.result) - } - - request.onerror = () => { - reject(new Error('获取数量失败')) - } - }) - } - - /** - * 删除最旧的图片 - */ - async deleteOldest(): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readwrite') - const objectStore = transaction.objectStore(STORE_NAME) - const index = objectStore.index('timestamp') - - // 获取最旧的记录 - const request = index.openCursor() - - request.onsuccess = () => { - const cursor = request.result - if (cursor) { - cursor.delete() - resolve() - } else { - resolve() - } - } - - request.onerror = () => { - reject(new Error('删除失败')) - } - }) - } - - /** - * 批量删除最旧的 N 张图片 - * @param count 要删除的图片数量 - */ - async deleteOldestBatch(count: number): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readwrite') - const objectStore = transaction.objectStore(STORE_NAME) - const index = objectStore.index('timestamp') - - let deletedCount = 0 - const request = index.openCursor() - - request.onsuccess = () => { - const cursor = request.result - if (cursor && deletedCount < count) { - cursor.delete() - deletedCount++ - cursor.continue() - } else { - resolve(deletedCount) - } - } - - request.onerror = () => { - reject(new Error('批量删除失败')) - } - }) - } - - /** - * 清空所有图片 - */ - async clear(): Promise { - if (!this.db) {await this.init()} - - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction([STORE_NAME], 'readwrite') - const objectStore = transaction.objectStore(STORE_NAME) - - const request = objectStore.clear() - - request.onsuccess = () => { - resolve() - } - - request.onerror = () => { - reject(new Error('清空失败')) - } - }) - } - - /** - * 关闭数据库连接 - */ - close(): void { - if (this.db) { - this.db.close() - this.db = null - } - } -} - -// 导出单例 -export const imageDB = new ImageDB() -