diff --git a/.gitignore b/.gitignore index 081ff24..093b432 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ bun.lockb dist .env .wrangler +.codex diff --git a/bun.lock b/bun.lock index 37b8b5f..f42a5b6 100644 --- a/bun.lock +++ b/bun.lock @@ -5,23 +5,29 @@ "": { "name": "textodon", "dependencies": { - "dompurify": "^3.3.1", + "dompurify": "^3.3.2", "emoji-datasource": "^16.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "wrangler": "3.90.0", + "wrangler": "^4.78.0", }, "devDependencies": { - "@types/dompurify": "^3.2.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", + "@vitejs/plugin-react": "^6.0.1", + "bun-types": "^1.3.11", + "esbuild": "^0.25.5", + "picomatch": "^4.0.4", + "rollup": "^4.59.0", "typescript": "^5.5.4", - "vite": "^5.4.0", - "vite-plugin-svgr": "^4.5.0", + "vite": "^8.0.3", + "vite-plugin-svgr": "^5.0.0", }, }, }, + "overrides": { + "picomatch": "^4.0.4", + }, "packages": { "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], @@ -39,8 +45,6 @@ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], @@ -51,83 +55,135 @@ "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], - - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], - "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20241106.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zxvaToi1m0qzAScrxFt7UvFVqU8DxrCO2CinM1yQkv5no7pA1HolpIrwZ0xOhR3ny64Is2s/J6BrRjpO5dM9Zw=="], + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg=="], - "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20241106.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-j3dg/42D/bPgfNP3cRUBxF+4waCKO/5YKwXNj+lnVOwHxDu+ne5pFw9TIkKYcWTcwn0ZUkbNZNM5rhJqRn4xbg=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260317.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g=="], - "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20241106.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Ih+Ye8E1DMBXcKrJktGfGztFqHKaX1CeByqshmTbODnWKHt6O65ax3oTecUwyC0+abuyraOpAtdhHNpFMhUkmw=="], + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260317.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg=="], - "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20241106.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mdQFPk4+14Yywn7n1xIzI+6olWM8Ybz10R7H3h+rk0XulMumCWUCy1CzIDauOx6GyIcSgKIibYMssVHZR30ObA=="], + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260317.1", "", { "os": "linux", "cpu": "x64" }, "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug=="], - "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20241106.1", "", { "os": "win32", "cpu": "x64" }, "sha512-4rtcss31E/Rb/PeFocZfr+B9i1MdrkhsTBWizh8siNR4KMmkslU2xs2wPaH1z8+ErxkOsHrKRa5EPLh5rIiFeg=="], + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260317.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw=="], - "@cloudflare/workers-shared": ["@cloudflare/workers-shared@0.8.0", "", { "dependencies": { "mime": "^3.0.0", "zod": "^3.22.3" } }, "sha512-1OvFkNtslaMZAJsaocTmbACApgmWv55uLpNj50Pn2MGcxdAjpqykXJFQw5tKc+lGV9TDZh9oO3Rsk17IEQDzIg=="], + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260317.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ=="], "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], - "@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="], + "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], - "@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], - "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -137,55 +193,105 @@ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], + + "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], + + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], + + "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], + + "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm" }, "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.12", "", { "os": "none", "cpu": "arm64" }, "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.12", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "x64" }, "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + + "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + + "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], "@svgr/babel-plugin-add-jsx-attribute": ["@svgr/babel-plugin-add-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g=="], @@ -211,21 +317,11 @@ "@svgr/plugin-jsx": ["@svgr/plugin-jsx@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "@svgr/hast-util-to-babel-ast": "8.0.0", "svg-parser": "^2.0.4" }, "peerDependencies": { "@svgr/core": "*" } }, "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA=="], - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], - - "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], - - "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], - - "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - - "@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], - - "@types/node-forge": ["@types/node-forge@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw=="], + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], @@ -235,49 +331,37 @@ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="], "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], "caniuse-lite": ["caniuse-lite@1.0.30001761", "", {}, "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g=="], - "capnp-ts": ["capnp-ts@0.7.0", "", { "dependencies": { "debug": "^4.3.1", "tslib": "^2.2.0" } }, "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g=="], - - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="], - - "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], + "dompurify": ["dompurify@3.3.3", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="], "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], @@ -289,36 +373,24 @@ "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - "get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="], - - "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - - "itty-time": ["itty-time@1.0.6", "", {}, "sha512-+P8IZaLLBtFv8hCkIjcymZOp4UJ+xW6bSlQsXGqrkmJh7vSiMFSlNne0mCYagEE0N7HDNR5jJBRxwN0oYv61Rw=="], - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -329,6 +401,32 @@ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], @@ -337,172 +435,148 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], - - "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - - "miniflare": ["miniflare@3.20241106.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "^8.8.0", "acorn-walk": "^8.2.0", "capnp-ts": "^0.7.0", "exit-hook": "^2.2.1", "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.4", "workerd": "1.20241106.1", "ws": "^8.18.0", "youch": "^3.2.2", "zod": "^3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-dM3RBlJE8rUFxnqlPCaFCq0E7qQqEQvKbYX7W/APGCK+rLcyLmEBzC4GQR/niXdNM/oV6gdg9AA50ghnn2ALuw=="], + "miniflare": ["miniflare@4.20260317.3", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260317.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-tK78D3X4q30/SXqVwMhWrUfH+ffRou9dJLC+jkhNy5zh1I7i7T4JH6xihOvYxdCSBavJ5fQXaaxDJz6orh09BA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], - "node-forge": ["node-forge@1.3.3", "", {}, "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - "ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="], - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], - - "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="], + "rolldown": ["rolldown@1.0.0-rc.12", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.12" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-x64": "1.0.0-rc.12", "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A=="], - "rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="], - - "rollup-plugin-node-polyfills": ["rollup-plugin-node-polyfills@0.2.1", "", { "dependencies": { "rollup-plugin-inject": "^3.0.0" } }, "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA=="], - - "rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="], + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - "selfsigned": ["selfsigned@2.4.1", "", { "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" } }, "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q=="], - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "sourcemap-codec": ["sourcemap-codec@1.4.8", "", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="], - - "stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="], - - "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "svg-parser": ["svg-parser@2.0.4", "", {}, "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], - - "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "unenv": ["unenv-nightly@2.0.0-20241111-080453-894aa31", "", { "dependencies": { "defu": "^6.1.4", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-0W39QQOQ9VE8kVVUpGwEG+pZcsCXk5wqNG6rDPE6Gr+fiA69LR0qERM61hW5KCOkC1/ArCFrfCGjwHyyv/bI0Q=="], + "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], - "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + "vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="], - "vite-plugin-svgr": ["vite-plugin-svgr@4.5.0", "", { "dependencies": { "@rollup/pluginutils": "^5.2.0", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0" }, "peerDependencies": { "vite": ">=2.6.0" } }, "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA=="], + "vite-plugin-svgr": ["vite-plugin-svgr@5.0.0", "", { "dependencies": { "@rollup/pluginutils": "^5.2.0", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0" }, "peerDependencies": { "vite": ">=3.0.0" } }, "sha512-CZFWDtbWSLnF6C+uv8u7E5Ao6UVQYBpJrS6212XsEod/Lm4ErhOoFc01/po4ie5hqvMCr5KYrlMrSGQQEtMtBg=="], - "workerd": ["workerd@1.20241106.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20241106.1", "@cloudflare/workerd-darwin-arm64": "1.20241106.1", "@cloudflare/workerd-linux-64": "1.20241106.1", "@cloudflare/workerd-linux-arm64": "1.20241106.1", "@cloudflare/workerd-windows-64": "1.20241106.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-1GdKl0kDw8rrirr/ThcK66Kbl4/jd4h8uHx5g7YHBrnenY5SX1UPuop2cnCzYUxlg55kPjzIqqYslz1muRFgFw=="], + "workerd": ["workerd@1.20260317.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260317.1", "@cloudflare/workerd-darwin-arm64": "1.20260317.1", "@cloudflare/workerd-linux-64": "1.20260317.1", "@cloudflare/workerd-linux-arm64": "1.20260317.1", "@cloudflare/workerd-windows-64": "1.20260317.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g=="], - "wrangler": ["wrangler@3.90.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/workers-shared": "0.8.0", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "blake3-wasm": "^2.1.5", "chokidar": "^4.0.1", "date-fns": "^4.1.0", "esbuild": "0.17.19", "itty-time": "^1.0.6", "miniflare": "3.20241106.1", "nanoid": "^3.3.3", "path-to-regexp": "^6.3.0", "resolve": "^1.22.8", "resolve.exports": "^2.0.2", "selfsigned": "^2.0.1", "source-map": "^0.6.1", "unenv": "npm:unenv-nightly@2.0.0-20241111-080453-894aa31", "workerd": "1.20241106.1", "xxhash-wasm": "^1.0.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20241106.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-E/6E9ORAl987+3kP8wDiE3L1lj9r4vQ32/dl5toIxIkSMssmPRQVdxqwgMxbxJrytbFNo8Eo6swgjd4y4nUaLg=="], + "wrangler": ["wrangler@4.78.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260317.3", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260317.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260317.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-He/vUhk4ih0D0eFmtNnlbT6Od8j+BEokaSR+oYjbVsH0SWIrIch+eHqfLRSBjBQaOoh6HCNxcafcIkBm2u0Hag=="], - "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - - "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], + + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], + + "@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="], + + "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "wrangler/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], - "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - "rollup-plugin-inject/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], + "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - "rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], + "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - "wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], + "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], + "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], + "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], + "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], + "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], + "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], + "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], + "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], + "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], + "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], + "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], + "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], + "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], + "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], + "wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], + "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], + "wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], + "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], + "wrangler/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], - "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], + "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], + "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], + "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], + "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], } } diff --git a/functions/api/preview.ts b/functions/api/preview.ts index f385824..e1d74a0 100644 --- a/functions/api/preview.ts +++ b/functions/api/preview.ts @@ -2,6 +2,7 @@ type Env = Record; const MAX_RESPONSE_BYTES = 512 * 1024; const REQUEST_TIMEOUT_MS = 5000; +const MAX_REDIRECTS = 2; const textDecoder = new TextDecoder("utf-8"); @@ -35,12 +36,16 @@ const extractTitle = (html: string): string | null => { return text || null; }; -const toAbsoluteUrl = (value: string | null, baseUrl: string): string | null => { +const toAbsoluteHttpUrl = (value: string | null, baseUrl: string): string | null => { if (!value) { return null; } try { - return new URL(value, baseUrl).toString(); + const url = new URL(value, baseUrl); + if (url.protocol !== "http:" && url.protocol !== "https:") { + return null; + } + return url.toString(); } catch { return null; } @@ -81,6 +86,13 @@ const isPrivateIpv4 = (host: string): boolean => { const isPrivateIpv6 = (host: string): boolean => { const normalized = host.toLowerCase(); + if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") { + return true; + } + if (normalized.startsWith("::ffff:")) { + const mappedIpv4 = normalized.slice(7); + return isIpAddress(mappedIpv4) && isPrivateIpv4(mappedIpv4); + } return ( normalized === "::1" || normalized.startsWith("fe80:") || @@ -100,6 +112,84 @@ const isBlockedHostname = (hostname: string): boolean => { return false; }; +const isYouTubeHost = (hostname: string): boolean => { + const lower = hostname.toLowerCase(); + return lower === "youtu.be" || lower === "youtube.com" || lower.endsWith(".youtube.com"); +}; + + +const isAllowedContentType = (contentType: string): boolean => { + const normalized = contentType.toLowerCase().split(";")[0]?.trim(); + return normalized === "text/html" || normalized === "application/xhtml+xml"; +}; + + +const fetchWithSafeRedirects = async ( + targetUrl: URL, + signal: AbortSignal, + redirectCount = 0 +): Promise => { + const response = await fetch(targetUrl.toString(), { + signal, + redirect: "manual", + headers: { + "User-Agent": "DeckLinkPreview/1.0", + Accept: "text/html,application/xhtml+xml" + } + }); + + if (response.status >= 300 && response.status < 400) { + if (redirectCount >= MAX_REDIRECTS) { + throw new Error("too_many_redirects"); + } + const location = response.headers.get("location"); + if (!location) { + throw new Error("invalid_redirect"); + } + let redirectedUrl: URL; + try { + redirectedUrl = new URL(location, targetUrl.toString()); + } catch { + throw new Error("invalid_redirect"); + } + const validatedRedirectUrl = isValidHttpUrl(redirectedUrl.toString()); + if (!validatedRedirectUrl || isBlockedHostname(validatedRedirectUrl.hostname)) { + throw new Error("blocked_redirect"); + } + return fetchWithSafeRedirects(validatedRedirectUrl, signal, redirectCount + 1); + } + + return response; +}; + + +const fetchYouTubeOEmbed = async (targetUrl: string): Promise<{ title: string; image: string | null } | null> => { + try { + const oembedUrl = new URL("https://www.youtube.com/oembed"); + oembedUrl.searchParams.set("url", targetUrl); + oembedUrl.searchParams.set("format", "json"); + const response = await fetch(oembedUrl.toString(), { + headers: { + Accept: "application/json" + } + }); + if (!response.ok) { + return null; + } + const data = (await response.json()) as { title?: string; thumbnail_url?: string }; + if (!data.title) { + return null; + } + return { + title: data.title, + image: data.thumbnail_url ?? null + }; + } catch { + return null; + } +}; + + const readResponseText = async (response: Response): Promise => { if (!response.body) { return response.text(); @@ -129,53 +219,63 @@ const readResponseText = async (response: Response): Promise => { return textDecoder.decode(combined); }; -const buildResponse = (body: Record, status = 200, cacheSeconds = 600): Response => { +const buildResponse = ( + body: Record, + status = 200, + cacheSeconds = 600, + allowedOrigin?: string +): Response => { + const headers: Record = { + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": `public, max-age=${cacheSeconds}`, + "Content-Security-Policy": "default-src 'none'", + "Referrer-Policy": "strict-origin-when-cross-origin", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", + Vary: "Origin" + }; + + if (allowedOrigin) { + headers["Access-Control-Allow-Origin"] = allowedOrigin; + } + return new Response(JSON.stringify(body), { status, - headers: { - "Content-Type": "application/json; charset=utf-8", - "Cache-Control": `public, max-age=${cacheSeconds}`, - "Access-Control-Allow-Origin": "*" - } + headers }); }; export const onRequestGet = async (context: { request: Request } & { env?: Env }) => { const requestUrl = new URL(context.request.url); + const allowedOrigin = requestUrl.origin; const urlParam = requestUrl.searchParams.get("url"); if (!urlParam) { - return buildResponse({ error: "missing_url" }, 400, 60); + return buildResponse({ error: "missing_url" }, 400, 60, allowedOrigin); } const targetUrl = isValidHttpUrl(urlParam); if (!targetUrl || isBlockedHostname(targetUrl.hostname)) { - return buildResponse({ error: "invalid_url" }, 400, 60); + return buildResponse({ error: "invalid_url" }, 400, 60, allowedOrigin); } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); try { - const response = await fetch(targetUrl.toString(), { - signal: controller.signal, - headers: { - "User-Agent": "DeckLinkPreview/1.0", - Accept: "text/html,application/xhtml+xml" - } - }); + const response = await fetchWithSafeRedirects(targetUrl, controller.signal); if (!response.ok) { - return buildResponse({ error: "fetch_failed", status: response.status }, 200, 60); + return buildResponse({ error: "fetch_failed", status: response.status }, 200, 60, allowedOrigin); } const contentType = response.headers.get("content-type") ?? ""; - if (!contentType.includes("text/html")) { - return buildResponse({ error: "unsupported_content" }, 200, 300); + if (!isAllowedContentType(contentType)) { + return buildResponse({ error: "unsupported_content" }, 200, 300, allowedOrigin); } const html = await readResponseText(response); if (!html) { - return buildResponse({ error: "empty_body" }, 200, 60); + return buildResponse({ error: "empty_body" }, 200, 60, allowedOrigin); } const ogTitle = extractMetaTagContent(html, "property", "og:title"); @@ -183,13 +283,23 @@ export const onRequestGet = async (context: { request: Request } & { env?: Env } const ogImageRaw = extractMetaTagContent(html, "property", "og:image"); const ogUrl = extractMetaTagContent(html, "property", "og:url"); const metaDescription = extractMetaTagContent(html, "name", "description"); - const title = ogTitle || extractTitle(html); + let title = ogTitle || extractTitle(html); const description = ogDescription || metaDescription; - const image = toAbsoluteUrl(ogImageRaw, targetUrl.toString()); - const canonicalUrl = toAbsoluteUrl(ogUrl, targetUrl.toString()) ?? targetUrl.toString(); + let image = toAbsoluteHttpUrl(ogImageRaw, targetUrl.toString()); + const canonicalUrl = toAbsoluteHttpUrl(ogUrl, targetUrl.toString()) ?? targetUrl.toString(); + + const shouldFetchYouTube = !title || title.trim() === "YouTube"; + if (shouldFetchYouTube && isYouTubeHost(targetUrl.hostname)) { + const oembed = await fetchYouTubeOEmbed(targetUrl.toString()); + if (oembed) { + title = title || oembed.title; + image = image || oembed.image; + } + } + if (!title) { - return buildResponse({ error: "missing_title" }, 200, 300); + return buildResponse({ error: "missing_title" }, 200, 300, allowedOrigin); } return buildResponse( @@ -200,13 +310,17 @@ export const onRequestGet = async (context: { request: Request } & { env?: Env } image: image || null }, 200, - 600 + 600, + allowedOrigin ); } catch (error) { + if (error instanceof Error && ["invalid_redirect", "blocked_redirect", "too_many_redirects"].includes(error.message)) { + return buildResponse({ error: error.message }, 200, 60, allowedOrigin); + } if (error instanceof Error && error.name === "AbortError") { - return buildResponse({ error: "timeout" }, 200, 60); + return buildResponse({ error: "timeout" }, 200, 60, allowedOrigin); } - return buildResponse({ error: "fetch_failed" }, 200, 60); + return buildResponse({ error: "fetch_failed" }, 200, 60, allowedOrigin); } finally { clearTimeout(timeout); } diff --git a/index.html b/index.html index cbfd991..9bed5b0 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ diff --git a/package.json b/package.json index 8908afa..7b22902 100644 --- a/package.json +++ b/package.json @@ -6,24 +6,31 @@ "scripts": { "dev": "vite", "build": "vite build", + "dev:preview": "bun run build && bunx wrangler pages dev dist", "preview": "vite preview", "test": "bun test", "test:watch": "bun test --watch" }, "dependencies": { - "dompurify": "^3.3.1", + "dompurify": "^3.3.2", "emoji-datasource": "^16.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "wrangler": "3.90.0" + "wrangler": "^4.78.0" }, "devDependencies": { - "@types/dompurify": "^3.2.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", + "@vitejs/plugin-react": "^6.0.1", + "bun-types": "^1.3.11", + "esbuild": "^0.25.5", + "picomatch": "^4.0.4", + "rollup": "^4.59.0", "typescript": "^5.5.4", - "vite": "^5.4.0", - "vite-plugin-svgr": "^4.5.0" + "vite": "^8.0.3", + "vite-plugin-svgr": "^5.0.0" + }, + "overrides": { + "picomatch": "^4.0.4" } } diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000..b3f337f --- /dev/null +++ b/public/_headers @@ -0,0 +1,5 @@ +/* + Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' https: data: blob:; media-src 'self' https: blob: data:; font-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self' https: wss:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self' blob: + X-Content-Type-Options: nosniff + X-Frame-Options: DENY + Referrer-Policy: strict-origin-when-cross-origin diff --git a/src/App.tsx b/src/App.tsx index 3427198..c5ff754 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,15 +19,56 @@ import { normalizeTimelineType } from "./ui/utils/timeline"; import { buildOptimisticReactionStatus, hasSameReactions } from "./ui/utils/reactions"; import { ColorScheme, ThemeMode, getStoredColorScheme, getStoredTheme, isColorScheme, isThemeMode } from "./ui/utils/theme"; import type { InfoModalType } from "./ui/types/info"; +import type { ReplyingTo } from "./ui/types/compose"; +import type { SectionDisplaySettings } from "./ui/types/section"; import logoUrl from "./ui/assets/textodon-icon-blue.png"; type Route = "home" | "terms" | "license" | "oss" | "shortcuts"; type SelectedTimelineStatus = { sectionId: string; statusId: string }; -type ProfileTarget = { status: Status; account: Account | null; zIndex: number }; +type ProfileTarget = { status: Status; account: Account | null; settings: SectionDisplaySettings; zIndex: number }; const SECTION_STORAGE_KEY = "textodon.sections"; const COMPOSE_ACCOUNT_KEY = "textodon.compose.accountId"; +const getStoredSectionSize = (): SectionDisplaySettings["sectionSize"] => { + try { + const stored = localStorage.getItem("textodon.sectionSize"); + if (stored === "medium" || stored === "large" || stored === "small") { + return stored; + } + } catch { + /* noop */ + } + return "small"; +}; + +const getDefaultSectionSettings = (): SectionDisplaySettings => { + let showProfileImages = true; + let showCustomEmojis = true; + let showReactions = true; + try { + showProfileImages = localStorage.getItem("textodon.profileImages") !== "off"; + } catch { + /* noop */ + } + try { + showCustomEmojis = localStorage.getItem("textodon.customEmojis") !== "off"; + } catch { + /* noop */ + } + try { + showReactions = localStorage.getItem("textodon.reactions") !== "off"; + } catch { + /* noop */ + } + return { + showProfileImages, + showCustomEmojis, + showReactions, + sectionSize: getStoredSectionSize() + }; +}; + const parseRoute = (): Route => { const hash = window.location.hash.replace(/^#/, ""); if (!hash || hash === "/") { @@ -44,22 +85,6 @@ const parseRoute = (): Route => { export const App = () => { const [themeMode, setThemeMode] = useState(() => getStoredTheme()); const [colorScheme, setColorScheme] = useState(() => getStoredColorScheme()); - const [sectionSize, setSectionSize] = useState<"small" | "medium" | "large">(() => { - const stored = localStorage.getItem("textodon.sectionSize"); - if (stored === "medium" || stored === "large" || stored === "small") { - return stored; - } - return "small"; - }); - const [showProfileImages, setShowProfileImages] = useState(() => { - return localStorage.getItem("textodon.profileImages") !== "off"; - }); - const [showCustomEmojis, setShowCustomEmojis] = useState(() => { - return localStorage.getItem("textodon.customEmojis") !== "off"; - }); - const [showMisskeyReactions, setShowMisskeyReactions] = useState(() => { - return localStorage.getItem("textodon.reactions") !== "off"; - }); const [showPomodoro, setShowPomodoro] = useState(() => { return localStorage.getItem("textodon.pomodoro") === "on"; }); @@ -91,6 +116,7 @@ export const App = () => { if (raw) { const parsed = JSON.parse(raw) as Array>; if (Array.isArray(parsed) && parsed.length > 0) { + const defaults = getDefaultSectionSettings(); return parsed.map((item) => ({ id: item.id || crypto.randomUUID(), accountId: item.accountId ?? null, @@ -100,13 +126,20 @@ export const App = () => { ? accountsState.accounts.find((account) => account.id === item.accountId)?.platform ?? null : null, false - ) + ), + settings: { + showProfileImages: item.settings?.showProfileImages ?? defaults.showProfileImages, + showCustomEmojis: item.settings?.showCustomEmojis ?? defaults.showCustomEmojis, + showReactions: item.settings?.showReactions ?? defaults.showReactions, + sectionSize: item.settings?.sectionSize ?? defaults.sectionSize + } })); } } } catch { /* noop */ } + const defaults = getDefaultSectionSettings(); if (accountsState.accounts.length === 0) { return []; } @@ -114,7 +147,8 @@ export const App = () => { { id: crypto.randomUUID(), accountId: accountsState.activeAccountId ?? accountsState.accounts[0]?.id ?? null, - timelineType: "home" + timelineType: "home", + settings: { ...defaults } } ]; }); @@ -135,6 +169,9 @@ export const App = () => { ); const [replyTarget, setReplyTarget] = useState(null); const [selectedStatus, setSelectedStatus] = useState(null); + const [selectedStatusSettings, setSelectedStatusSettings] = useState(() => + getDefaultSectionSettings() + ); const [selectedStatusThreadAccount, setSelectedStatusThreadAccount] = useState(null); const [selectedTimelineStatus, setSelectedTimelineStatus] = useState(null); const [profileTargets, setProfileTargets] = useState([]); @@ -152,6 +189,9 @@ export const App = () => { const replySummary = replyTarget ? `@${formatReplyHandle(replyTarget.accountHandle, replyTarget.accountUrl, composeAccount?.instanceUrl ?? "")} · ${replyTarget.content.slice(0, 80)}` : null; + const replyingTo: ReplyingTo | null = replyTarget + ? { id: replyTarget.id, summary: replySummary ?? "", spoilerText: replyTarget.spoilerText } + : null; const [route, setRoute] = useState(() => parseRoute()); const timelineListeners = useRef void>>>(new Map()); const previousAccountIds = useRef>(new Set()); @@ -380,11 +420,6 @@ export const App = () => { localStorage.setItem("textodon.colorScheme", colorScheme); }, [colorScheme]); - useEffect(() => { - document.documentElement.dataset.sectionSize = sectionSize; - localStorage.setItem("textodon.sectionSize", sectionSize); - }, [sectionSize]); - useEffect(() => { try { if (composeAccountId) { @@ -397,18 +432,6 @@ export const App = () => { } }, [composeAccountId]); - useEffect(() => { - localStorage.setItem("textodon.profileImages", showProfileImages ? "on" : "off"); - }, [showProfileImages]); - - useEffect(() => { - localStorage.setItem("textodon.customEmojis", showCustomEmojis ? "on" : "off"); - }, [showCustomEmojis]); - - useEffect(() => { - localStorage.setItem("textodon.reactions", showMisskeyReactions ? "on" : "off"); - }, [showMisskeyReactions]); - useEffect(() => { localStorage.setItem("textodon.pomodoro", showPomodoro ? "on" : "off"); }, [showPomodoro]); @@ -823,11 +846,17 @@ export const App = () => { (account) => !previousAccountIds.current.has(account.id) ); if (addedAccounts.length > 0) { + const defaults = getDefaultSectionSettings(); setSections((current) => { const next = [...current]; addedAccounts.forEach((account) => { if (!next.some((section) => section.accountId === account.id)) { - next.push({ id: crypto.randomUUID(), accountId: account.id, timelineType: "home" }); + next.push({ + id: crypto.randomUUID(), + accountId: account.id, + timelineType: "home", + settings: { ...defaults } + }); } }); return next; @@ -885,20 +914,31 @@ export const App = () => { setComposeAccountId(account.id); setReplyTarget(status); const formattedHandle = formatReplyHandle(status.accountHandle, status.accountUrl, account.instanceUrl); - setMentionSeed(`@${formattedHandle}`); + setMentionSeed(`@${formattedHandle} `); setSelectedStatus(null); }; - const handleStatusClick = (status: Status, columnAccount: Account | null) => { + const handleStatusClick = ( + status: Status, + columnAccount: Account | null, + settings: SectionDisplaySettings + ) => { setSelectedStatus(status); setStatusModalZIndex(nextModalZIndexRef.current++); setSelectedStatusThreadAccount(columnAccount); + setSelectedStatusSettings(settings); }; - const handleProfileOpen = useCallback((target: Status, columnAccount: Account | null) => { - const zIndex = nextModalZIndexRef.current++; - setProfileTargets((current) => [...current, { status: target, account: columnAccount, zIndex }]); - }, []); + const handleProfileOpen = useCallback( + (target: Status, columnAccount: Account | null, settings: SectionDisplaySettings) => { + const zIndex = nextModalZIndexRef.current++; + setProfileTargets((current) => [ + ...current, + { status: target, account: columnAccount, settings, zIndex } + ]); + }, + [] + ); const handleCloseProfileModal = useCallback((index?: number) => { setProfileTargets((current) => { @@ -961,15 +1001,17 @@ export const App = () => { /> ); - const addSectionAt = (index: number) => { + const addSectionAt = (index: number, baseSettings?: SectionDisplaySettings) => { const defaultAccountId = composeAccountId ?? accountsState.accounts[0]?.id ?? null; + const settings = baseSettings ?? getDefaultSectionSettings(); setSections((current) => { const next = [...current]; const insertIndex = Math.max(0, Math.min(index, next.length)); next.splice(insertIndex, 0, { id: crypto.randomUUID(), accountId: defaultAccountId, - timelineType: "home" + timelineType: "home", + settings: { ...settings } }); return next; }); @@ -984,7 +1026,8 @@ export const App = () => { addSectionAt(sections.length); return; } - addSectionAt(direction === "left" ? index : index + 1); + const baseSettings = sections[index]?.settings; + addSectionAt(direction === "left" ? index : index + 1, baseSettings); }; const moveSection = (sectionId: string, direction: "left" | "right") => { @@ -1041,6 +1084,19 @@ export const App = () => { ); }; + const updateSectionSettings = useCallback( + (sectionId: string, updates: Partial) => { + setSections((current) => + current.map((section) => + section.id === sectionId + ? { ...section, settings: { ...section.settings, ...updates } } + : section + ) + ); + }, + [] + ); + useEffect(() => { try { localStorage.setItem(SECTION_STORAGE_KEY, JSON.stringify(sections)); @@ -1099,7 +1155,7 @@ export const App = () => { account={composeAccount} api={services.api} onSubmit={handleSubmit} - replyingTo={replyTarget ? { id: replyTarget.id, summary: replySummary ?? "" } : null} + replyingTo={replyingTo} onCancelReply={() => { setReplyTarget(null); setMentionSeed(null); @@ -1217,17 +1273,16 @@ export const App = () => { className="timeline-board" ref={timelineBoardRef} > - {sections.map((section, index) => { - const sectionAccount = - section.accountId - ? accountsState.accounts.find((account) => account.id === section.accountId) ?? null - : null; - const shouldShowReactions = showMisskeyReactions; - const selectedStatusId = - selectedTimelineStatus?.sectionId === section.id - ? selectedTimelineStatus.statusId - : null; - return ( + {sections.map((section, index) => { + const sectionAccount = + section.accountId + ? accountsState.accounts.find((account) => account.id === section.accountId) ?? null + : null; + const selectedStatusId = + selectedTimelineStatus?.sectionId === section.id + ? selectedTimelineStatus.statusId + : null; + return ( { onMoveSection={moveSection} onTimelineItemsChange={handleTimelineItemsChange} onSelectStatus={handleSelectStatus} + onUpdateSectionSettings={updateSectionSettings} canMoveLeft={index > 0} canMoveRight={index < sections.length - 1} canRemoveSection={sections.length > 1} timelineType={section.timelineType} - showProfileImage={showProfileImages} - showCustomEmojis={showCustomEmojis} - showReactions={shouldShowReactions} registerTimelineListener={registerTimelineListener} unregisterTimelineListener={unregisterTimelineListener} registerTimelineShortcutHandler={registerTimelineShortcutHandler} @@ -1294,7 +1347,7 @@ export const App = () => { composeAccountSelector={composeAccountSelector} api={services.api} onSubmit={handleSubmit} - replyingTo={replyTarget ? { id: replyTarget.id, summary: replySummary ?? "" } : null} + replyingTo={replyingTo} onCancelReply={() => { setReplyTarget(null); setMentionSeed(null); @@ -1331,14 +1384,6 @@ export const App = () => { setColorScheme(value); } }} - showProfileImages={showProfileImages} - onToggleProfileImages={setShowProfileImages} - showCustomEmojis={showCustomEmojis} - onToggleCustomEmojis={setShowCustomEmojis} - showMisskeyReactions={showMisskeyReactions} - onToggleMisskeyReactions={setShowMisskeyReactions} - sectionSize={sectionSize} - onSectionSizeChange={setSectionSize} showPomodoro={showPomodoro} onTogglePomodoro={setShowPomodoro} pomodoroFocus={pomodoroFocus} @@ -1359,11 +1404,12 @@ export const App = () => { isTopmost={index === profileTargets.length - 1} onClose={() => handleCloseProfileModal(index)} onReply={handleReply} - onStatusClick={(status) => handleStatusClick(status, target.account)} + onStatusClick={(status, account, settings) => handleStatusClick(status, account, settings)} onProfileClick={handleProfileOpen} - showProfileImage={showProfileImages} - showCustomEmojis={showCustomEmojis} - showReactions={showMisskeyReactions} + showProfileImage={target.settings.showProfileImages} + showCustomEmojis={target.settings.showCustomEmojis} + showReactions={target.settings.showReactions} + sectionSettings={target.settings} /> ))} @@ -1451,9 +1497,10 @@ export const App = () => { } activeAccountHandle={composeAccount?.handle ?? ""} activeAccountUrl={composeAccount?.url ?? null} - showProfileImage={showProfileImages} - showCustomEmojis={showCustomEmojis} - showReactions={showMisskeyReactions} + showProfileImage={selectedStatusSettings.showProfileImages} + showCustomEmojis={selectedStatusSettings.showCustomEmojis} + showReactions={selectedStatusSettings.showReactions} + sectionSettings={selectedStatusSettings} /> ) : null} diff --git a/src/infra/MastodonHttpClient.ts b/src/infra/MastodonHttpClient.ts index f0625b0..c9b37cf 100644 --- a/src/infra/MastodonHttpClient.ts +++ b/src/infra/MastodonHttpClient.ts @@ -443,6 +443,29 @@ export class MastodonHttpClient implements MastodonApi { }; } + async translateStatus(account: Account, statusId: string) { + const response = await fetch(`${account.instanceUrl}/api/v1/statuses/${statusId}/translate`, { + method: "POST", + headers: buildHeaders(account) + }); + if (!response.ok) { + throw new Error("번역에 실패했습니다."); + } + const data = (await response.json()) as Record; + const htmlContent = typeof data.content === "string" ? data.content : ""; + const sourceLanguage = + typeof data.detected_source_language === "string" ? data.detected_source_language : null; + const targetLanguage = typeof data.target_language === "string" ? data.target_language : null; + const provider = typeof data.provider === "string" ? data.provider : null; + return { + content: htmlContent, + htmlContent, + sourceLanguage, + targetLanguage, + provider + }; + } + async reblog(account: Account, statusId: string): Promise { return this.postAction(account, statusId, "reblog"); } diff --git a/src/infra/MisskeyHttpClient.ts b/src/infra/MisskeyHttpClient.ts index 9230568..5af3ef7 100644 --- a/src/infra/MisskeyHttpClient.ts +++ b/src/infra/MisskeyHttpClient.ts @@ -316,6 +316,30 @@ export class MisskeyHttpClient implements MastodonApi { return data.map((item) => mapMisskeyStatusWithInstance(item, account.instanceUrl)); } + async fetchBookmarks(account: Account, limit: number = 20, maxId?: string): Promise { + const response = await fetch(`${normalizeInstanceUrl(account.instanceUrl)}/api/i/favorites`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify( + buildBody(account, { + limit, + untilId: maxId + }) + ) + }); + if (!response.ok) { + throw new Error("북마크를 불러오지 못했습니다."); + } + const data = (await response.json()) as unknown[]; + return data.map((item) => { + const typed = item && typeof item === "object" ? (item as Record) : null; + const note = typed && typed.note ? typed.note : item; + return mapMisskeyStatusWithInstance(note, account.instanceUrl); + }); + } + async uploadMedia(account: Account, file: File): Promise { const formData = new FormData(); formData.append("i", account.accessToken); @@ -338,6 +362,10 @@ export class MisskeyHttpClient implements MastodonApi { return id; } + async fetchThreadContext(account: Account, statusId: string): Promise { + return this.fetchConversation(account, statusId); + } + async fetchConversation(account: Account, noteId: string): Promise { const response = await fetch(`${normalizeInstanceUrl(account.instanceUrl)}/api/notes/conversation`, { method: "POST", @@ -421,23 +449,22 @@ export class MisskeyHttpClient implements MastodonApi { } async favourite(account: Account, statusId: string): Promise { - try { - await this.postSimple(account, "/api/notes/reactions/create", { - noteId: statusId, - reaction: DEFAULT_REACTION - }); - } catch { - await this.postSimple(account, "/api/notes/favorites/create", { noteId: statusId }); - } + await this.postSimple(account, "/api/notes/favorites/create", { noteId: statusId }); + return this.fetchNote(account, statusId); + } + + async bookmark(account: Account, statusId: string): Promise { + await this.postSimple(account, "/api/notes/favorites/create", { noteId: statusId }); + return this.fetchNote(account, statusId); + } + + async unbookmark(account: Account, statusId: string): Promise { + await this.postSimple(account, "/api/notes/favorites/delete", { noteId: statusId }); return this.fetchNote(account, statusId); } async unfavourite(account: Account, statusId: string): Promise { - try { - await this.postSimple(account, "/api/notes/reactions/delete", { noteId: statusId }); - } catch { - await this.postSimple(account, "/api/notes/favorites/delete", { noteId: statusId }); - } + await this.postSimple(account, "/api/notes/favorites/delete", { noteId: statusId }); return this.fetchNote(account, statusId); } @@ -454,6 +481,77 @@ export class MisskeyHttpClient implements MastodonApi { return this.fetchNote(account, statusId); } + async fetchNoteState( + account: Account, + noteId: string + ): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }> { + const response = await fetch(`${normalizeInstanceUrl(account.instanceUrl)}/api/notes/state`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(buildBody(account, { noteId })) + }); + if (!response.ok) { + throw new Error("게시물 상태를 불러오지 못했습니다."); + } + const state = (await response.json()) as Record; + const isFavorited = Boolean(state.isFavorited ?? false); + const isReblogged = Boolean(state.isRenoted ?? state.isReblogged ?? false); + return { + isFavourited: isFavorited, + isReblogged, + bookmarked: isFavorited + }; + } + + async translateStatus(account: Account, statusId: string) { + const response = await fetch(`${normalizeInstanceUrl(account.instanceUrl)}/api/notes/translate`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(buildBody(account, { noteId: statusId })) + }); + if (!response.ok) { + throw new Error("번역에 실패했습니다."); + } + const data = (await response.json()) as Record; + const content = + typeof data.text === "string" + ? data.text + : typeof data.translatedText === "string" + ? data.translatedText + : typeof data.content === "string" + ? data.content + : typeof data.translation === "string" + ? data.translation + : ""; + if (!content) { + throw new Error("번역 결과를 확인할 수 없습니다."); + } + const sourceLanguage = + typeof data.sourceLang === "string" + ? data.sourceLang + : typeof data.detectedLanguage === "string" + ? data.detectedLanguage + : null; + const targetLanguage = + typeof data.targetLang === "string" + ? data.targetLang + : typeof data.targetLanguage === "string" + ? data.targetLanguage + : null; + const provider = typeof data.provider === "string" ? data.provider : null; + return { + content, + htmlContent: null, + sourceLanguage, + targetLanguage, + provider + }; + } + async reblog(account: Account, statusId: string): Promise { await this.postSimple(account, "/api/notes/create", { renoteId: statusId }); return this.fetchNote(account, statusId); diff --git a/src/infra/UnifiedApiClient.ts b/src/infra/UnifiedApiClient.ts index fd8ba76..b677c69 100644 --- a/src/infra/UnifiedApiClient.ts +++ b/src/infra/UnifiedApiClient.ts @@ -1,6 +1,6 @@ import type { Account, ThreadContext, TimelineType, InstanceInfo } from "../domain/types"; import type { CustomEmoji } from "../domain/types"; -import type { CreateStatusInput, MastodonApi } from "../services/MastodonApi"; +import type { CreateStatusInput, MastodonApi, StatusTranslation } from "../services/MastodonApi"; export class UnifiedApiClient implements MastodonApi { constructor( @@ -125,4 +125,15 @@ export class UnifiedApiClient implements MastodonApi { async fetchThreadContext(account: Account, statusId: string): Promise { return this.getClient(account).fetchThreadContext(account, statusId); } + + fetchNoteState( + account: Account, + noteId: string + ): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }> { + return this.getClient(account).fetchNoteState(account, noteId); + } + + translateStatus(account: Account, statusId: string): Promise { + return this.getClient(account).translateStatus(account, statusId); + } } diff --git a/src/infra/misskeyMapper.ts b/src/infra/misskeyMapper.ts index 18b8036..c3d8131 100644 --- a/src/infra/misskeyMapper.ts +++ b/src/infra/misskeyMapper.ts @@ -460,7 +460,7 @@ export const mapMisskeyStatusWithInstance = (raw: unknown, instanceUrl?: string) reactions, reblogged, favourited, - bookmarked: false, + bookmarked: Boolean(value.isFavorited ?? false), inReplyToId: value.replyId ? String(value.replyId) : null, mentions, mediaAttachments, diff --git a/src/services/MastodonApi.ts b/src/services/MastodonApi.ts index 54fa689..e82f3dc 100644 --- a/src/services/MastodonApi.ts +++ b/src/services/MastodonApi.ts @@ -9,6 +9,14 @@ export type CreateStatusInput = { spoilerText?: string; }; +export type StatusTranslation = { + content: string; + htmlContent?: string | null; + sourceLanguage?: string | null; + targetLanguage?: string | null; + provider?: string | null; +}; + export interface MastodonApi { verifyAccount( account: Account @@ -44,4 +52,5 @@ export interface MastodonApi { account: Account, noteId: string ): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }>; + translateStatus(account: Account, statusId: string): Promise; } diff --git a/src/ui/components/ComposeBox.tsx b/src/ui/components/ComposeBox.tsx index 67e947c..d315b72 100644 --- a/src/ui/components/ComposeBox.tsx +++ b/src/ui/components/ComposeBox.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { Account, Visibility } from "../../domain/types"; import type { MastodonApi } from "../../services/MastodonApi"; +import type { ReplyingTo } from "../types/compose"; import { useEmojiManager, type EmojiItem } from "../hooks/useEmojiManager"; import { useImageZoom } from "../hooks/useImageZoom"; import { useClickOutside } from "../hooks/useClickOutside"; @@ -49,7 +50,7 @@ export const ComposeBox = ({ files: File[]; spoilerText: string; }) => Promise; - replyingTo: { id: string; summary: string } | null; + replyingTo: ReplyingTo | null; onCancelReply: () => void; mentionText: string | null; accountSelector?: React.ReactNode; @@ -89,6 +90,8 @@ export const ComposeBox = ({ const emojiToggleRef = useRef(null); const cwToggleRef = useRef(null); const emojiPanelRef = useRef(null); + const submitFocusTargetRef = useRef<"textarea" | "cw" | null>(null); + const skipReplyResetRef = useRef(false); // useImageZoom 훅 사용 const { @@ -264,6 +267,10 @@ export const ComposeBox = ({ return; } + const activeElement = document.activeElement; + submitFocusTargetRef.current = activeElement === cwInputRef.current ? "cw" : "textarea"; + const shouldKeepCwOpen = cwEnabled; + setIsSubmitting(true); try { const ok = await onSubmit({ @@ -275,8 +282,9 @@ export const ComposeBox = ({ }); if (ok) { setText(""); - setCwText(""); - setCwEnabled(false); + if (shouldKeepCwOpen) { + skipReplyResetRef.current = true; + } setAttachments((current) => { current.forEach((item) => URL.revokeObjectURL(item.previewUrl)); return []; @@ -285,6 +293,14 @@ export const ComposeBox = ({ } } finally { setIsSubmitting(false); + requestAnimationFrame(() => { + if (submitFocusTargetRef.current === "cw" && cwEnabled) { + cwInputRef.current?.focus(); + } else { + textareaRef.current?.focus(); + } + submitFocusTargetRef.current = null; + }); } }; @@ -302,6 +318,26 @@ export const ComposeBox = ({ } }, [mentionText]); + useEffect(() => { + if (!replyingTo) { + if (skipReplyResetRef.current) { + skipReplyResetRef.current = false; + return; + } + setCwEnabled(false); + setCwText(""); + return; + } + skipReplyResetRef.current = false; + if (!replyingTo.spoilerText) { + setCwEnabled(false); + setCwText(""); + return; + } + setCwEnabled(true); + setCwText(replyingTo.spoilerText); + }, [replyingTo?.id, replyingTo?.spoilerText]); + // 이모지 패널이 열리면 이모지 로드 useEffect(() => { if (emojiPanelOpen && account) { diff --git a/src/ui/components/MobileMenus.tsx b/src/ui/components/MobileMenus.tsx index 81b3992..4ed12be 100644 --- a/src/ui/components/MobileMenus.tsx +++ b/src/ui/components/MobileMenus.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from "react"; import type { Account, Visibility } from "../../domain/types"; import type { MastodonApi } from "../../services/MastodonApi"; import type { OAuthClient } from "../../services/OAuthClient"; +import type { ReplyingTo } from "../types/compose"; import { AccountAdd } from "./AccountAdd"; import { ComposeBox } from "./ComposeBox"; @@ -18,7 +19,7 @@ type MobileComposeMenuProps = { files: File[]; spoilerText: string; }) => Promise; - replyingTo: { id: string; summary: string } | null; + replyingTo: ReplyingTo | null; onCancelReply: () => void; mentionText: string | null; }; diff --git a/src/ui/components/PomodoroTimer.tsx b/src/ui/components/PomodoroTimer.tsx index 98ce46d..a68dcf9 100644 --- a/src/ui/components/PomodoroTimer.tsx +++ b/src/ui/components/PomodoroTimer.tsx @@ -130,6 +130,8 @@ export const PomodoroTimer = ({ } }); const [selectedTodoId, setSelectedTodoId] = useState(null); + const [draggingTodoId, setDraggingTodoId] = useState(null); + const [dragOverTodoId, setDragOverTodoId] = useState(null); const todoListRef = useRef(null); const todoInputRef = useRef(null); @@ -461,6 +463,55 @@ export const PomodoroTimer = ({ const displayedTodos = useMemo(() => todoItems, [todoItems]); + const moveTodoByIndex = useCallback((fromIndex: number, toIndex: number) => { + if (fromIndex === toIndex) { + return; + } + setTodoItems((prev) => { + if (fromIndex < 0 || toIndex < 0 || fromIndex >= prev.length || toIndex >= prev.length) { + return prev; + } + const next = [...prev]; + const [movedItem] = next.splice(fromIndex, 1); + if (!movedItem) { + return prev; + } + next.splice(toIndex, 0, movedItem); + return next; + }); + }, []); + + const moveTodoById = useCallback((draggedId: string, targetId: string) => { + if (draggedId === targetId) { + return; + } + const fromIndex = displayedTodos.findIndex((item) => item.id === draggedId); + const toIndex = displayedTodos.findIndex((item) => item.id === targetId); + if (fromIndex === -1 || toIndex === -1) { + return; + } + moveTodoByIndex(fromIndex, toIndex); + }, [displayedTodos, moveTodoByIndex]); + + const moveSelectedTodo = useCallback( + (direction: "up" | "down") => { + if (!selectedTodoId) { + return false; + } + const currentIndex = displayedTodos.findIndex((item) => item.id === selectedTodoId); + if (currentIndex === -1) { + return false; + } + const nextIndex = direction === "up" ? currentIndex - 1 : currentIndex + 1; + if (nextIndex < 0 || nextIndex >= displayedTodos.length) { + return false; + } + moveTodoByIndex(currentIndex, nextIndex); + return true; + }, + [displayedTodos, moveTodoByIndex, selectedTodoId] + ); + const selectTodo = useCallback( (id: string) => { setSelectedTodoId(id); @@ -469,6 +520,52 @@ export const PomodoroTimer = ({ [onRequestClearTimelineSelection] ); + const handleTodoDragStart = useCallback( + (event: React.DragEvent, id: string) => { + setDraggingTodoId(id); + setDragOverTodoId(id); + setSelectedTodoId(id); + onRequestClearTimelineSelection?.(); + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("text/plain", id); + }, + [onRequestClearTimelineSelection] + ); + + const handleTodoDragOver = useCallback( + (event: React.DragEvent, id: string) => { + if (!draggingTodoId) { + return; + } + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + if (draggingTodoId !== id && dragOverTodoId !== id) { + setDragOverTodoId(id); + } + }, + [dragOverTodoId, draggingTodoId] + ); + + const handleTodoDrop = useCallback( + (event: React.DragEvent, targetId: string) => { + event.preventDefault(); + const draggedId = draggingTodoId; + if (!draggedId) { + return; + } + moveTodoById(draggedId, targetId); + setSelectedTodoId(draggedId); + setDraggingTodoId(null); + setDragOverTodoId(null); + }, + [draggingTodoId, moveTodoById] + ); + + const handleTodoDragEnd = useCallback(() => { + setDraggingTodoId(null); + setDragOverTodoId(null); + }, []); + const handleTodoKeyDown = useCallback( (event: React.KeyboardEvent) => { const target = event.target; @@ -508,6 +605,20 @@ export const PomodoroTimer = ({ return; } + if ( + (key === "ArrowUp" || key === "ArrowDown") && + event.altKey && + !event.ctrlKey && + !event.metaKey && + !event.shiftKey + ) { + const moved = moveSelectedTodo(key === "ArrowUp" ? "up" : "down"); + if (moved) { + event.preventDefault(); + } + return; + } + if (key === "ArrowRight") { if (!selectedTodoId || !onRequestSelectTimelineAtY) { return; @@ -545,7 +656,7 @@ export const PomodoroTimer = ({ event.preventDefault(); selectTodo(displayedTodos[nextIndex].id); }, - [displayedTodos, handleRemoveTodo, handleToggleTodo, onRequestSelectTimelineAtY, selectTodo, selectedTodoId] + [displayedTodos, handleRemoveTodo, handleToggleTodo, moveSelectedTodo, onRequestSelectTimelineAtY, selectTodo, selectedTodoId] ); const handleTodoInputKeyDown = useCallback( @@ -623,18 +734,24 @@ export const PomodoroTimer = ({ ref={todoListRef} tabIndex={displayedTodos.length > 0 ? 0 : -1} onKeyDownCapture={handleTodoKeyDown} - title="↑/↓ 이동 · Space 완료 · D 삭제 · → 타임라인 이동 · ESC 선택 해제" + title="↑/↓ 이동 · Alt+↑/↓ 순서 변경 · Space 완료 · D 삭제 · → 타임라인 이동 · ESC 선택 해제" > {displayedTodos.map((item) => (
{ selectTodo(item.id); todoListRef.current?.focus(); }} + onDragStart={(event) => handleTodoDragStart(event, item.id)} + onDragOver={(event) => handleTodoDragOver(event, item.id)} + onDrop={(event) => handleTodoDrop(event, item.id)} + onDragEnd={handleTodoDragEnd} > void; onReply: (status: Status, account: Account | null) => void; - onStatusClick: (status: Status) => void; - onProfileClick: (status: Status, account: Account | null) => void; + onStatusClick: (status: Status, account: Account | null, settings: SectionDisplaySettings) => void; + onProfileClick: (status: Status, account: Account | null, settings: SectionDisplaySettings) => void; showProfileImage: boolean; showCustomEmojis: boolean; showReactions: boolean; + sectionSettings: SectionDisplaySettings; }) => { const [profile, setProfile] = useState(null); const [profileError, setProfileError] = useState(null); @@ -985,8 +988,8 @@ export const ProfileModal = ({ onToggleBookmark={handleToggleBookmark} onDelete={handleDeleteStatus} onReact={handleReact} - onStatusClick={onStatusClick} - onProfileClick={(target) => onProfileClick(target, account)} + onStatusClick={(target) => onStatusClick(target, account, sectionSettings)} + onProfileClick={(target) => onProfileClick(target, account, sectionSettings)} onUpdateStatus={updateItem} activeHandle={activeHandle} activeAccountHandle={account?.handle ?? ""} diff --git a/src/ui/components/SettingsModal.tsx b/src/ui/components/SettingsModal.tsx index 37f6395..ac3cc08 100644 --- a/src/ui/components/SettingsModal.tsx +++ b/src/ui/components/SettingsModal.tsx @@ -16,14 +16,6 @@ type SettingsModalProps = { onThemeChange: (value: string) => void; colorScheme: ColorScheme; onColorSchemeChange: (value: string) => void; - showProfileImages: boolean; - onToggleProfileImages: (value: boolean) => void; - showCustomEmojis: boolean; - onToggleCustomEmojis: (value: boolean) => void; - showMisskeyReactions: boolean; - onToggleMisskeyReactions: (value: boolean) => void; - sectionSize: "small" | "medium" | "large"; - onSectionSizeChange: (value: "small" | "medium" | "large") => void; showPomodoro: boolean; onTogglePomodoro: (value: boolean) => void; pomodoroFocus: number; @@ -48,14 +40,6 @@ export const SettingsModal = ({ onThemeChange, colorScheme, onColorSchemeChange, - showProfileImages, - onToggleProfileImages, - showCustomEmojis, - onToggleCustomEmojis, - showMisskeyReactions, - onToggleMisskeyReactions, - sectionSize, - onSectionSizeChange, showPomodoro, onTogglePomodoro, pomodoroFocus, @@ -153,64 +137,6 @@ export const SettingsModal = ({
-
-
- 프로필 이미지 표시 -

피드에서 사용자 프로필 이미지를 보여줍니다.

-
- -
-
-
- 커스텀 이모지 표시 -

사용자 이름과 본문에 커스텀 이모지를 표시합니다.

-
- -
-
-
- 리액션 표시 -

리액션 정보를 지원하는 서버에서 받은 리액션을 보여줍니다.

-
- -
-
-
- 섹션 폭 -

타임라인 섹션의 가로 폭을 조절합니다.

-
- -
뽀모도로 타이머 diff --git a/src/ui/components/StatusModal.tsx b/src/ui/components/StatusModal.tsx index 8e58b12..5471459 100644 --- a/src/ui/components/StatusModal.tsx +++ b/src/ui/components/StatusModal.tsx @@ -4,6 +4,7 @@ import type { MastodonApi } from "../../services/MastodonApi"; import { TimelineItem } from "./TimelineItem"; import BoostIcon from "../assets/boost-icon.svg?react"; import { useToast } from "../state/ToastContext"; +import type { SectionDisplaySettings } from "../types/section"; export const StatusModal = ({ status, @@ -24,7 +25,8 @@ export const StatusModal = ({ activeAccountUrl, showProfileImage, showCustomEmojis, - showReactions + showReactions, + sectionSettings }: { status: Status; account: Account | null; @@ -37,7 +39,7 @@ export const StatusModal = ({ onToggleReblog: (status: Status) => void; onToggleBookmark: (status: Status) => void; onDelete?: (status: Status) => void; - onProfileClick?: (status: Status, account: Account | null) => void; + onProfileClick?: (status: Status, account: Account | null, settings: SectionDisplaySettings) => void; onUpdateStatus?: (status: Status) => void; activeHandle: string; activeAccountHandle: string; @@ -45,6 +47,7 @@ export const StatusModal = ({ showProfileImage: boolean; showCustomEmojis: boolean; showReactions: boolean; + sectionSettings: SectionDisplaySettings; }) => { const displayStatus = status.reblog ?? status; const boostedBy = status.reblog ? status.boostedBy : null; @@ -116,9 +119,9 @@ export const StatusModal = ({ if (!onProfileClick) { return; } - onProfileClick(target, threadAccount ?? account ?? null); + onProfileClick(target, threadAccount ?? account ?? null, sectionSettings); }, - [account, onProfileClick, threadAccount] + [account, onProfileClick, sectionSettings, threadAccount] ); // 스레드 컨텍스트 상태 diff --git a/src/ui/components/TimelineItem.tsx b/src/ui/components/TimelineItem.tsx index 9476df8..84665c1 100644 --- a/src/ui/components/TimelineItem.tsx +++ b/src/ui/components/TimelineItem.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { Account, CustomEmoji, LinkCard, MediaAttachment, Mention, ReactionInput, Status } from "../../domain/types"; -import type { MastodonApi } from "../../services/MastodonApi"; +import type { MastodonApi, StatusTranslation } from "../../services/MastodonApi"; import { sanitizeHtml } from "../utils/htmlSanitizer"; import { renderMarkdown } from "../utils/markdown"; import { renderTextWithLinks, type MentionLink } from "../utils/linkify"; @@ -53,6 +53,38 @@ const extractFirstUrl = (text: string): string | null => { return `https://${value}`; }; +const decodeHtmlEntities = (value: string): string => { + const decodeCodePoint = (match: string, input: string, radix: number): string => { + const codePoint = Number.parseInt(input, radix); + if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) { + return match; + } + try { + return String.fromCodePoint(codePoint); + } catch { + return match; + } + }; + + return value + .replace(/&#(\d+);/g, (match, decimal) => decodeCodePoint(match, decimal, 10)) + .replace(/&#x([0-9a-fA-F]+);/g, (match, hexadecimal) => decodeCodePoint(match, hexadecimal, 16)) + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/&/g, "&"); +}; + +const normalizePreviewText = (value: string | null | undefined): string | null => { + if (!value) { + return null; + } + const decoded = decodeHtmlEntities(value); + const normalized = decoded.replace(/\s+/g, " ").trim(); + return normalized || null; +}; + const MediaVideo = ({ id, src, @@ -336,9 +368,13 @@ export const TimelineItem = ({ const [menuOpen, setMenuOpen] = useState(false); const [favouriteState, setFavouriteState] = useState(false); const [previewCard, setPreviewCard] = useState(displayStatus.card ?? null); + const [translation, setTranslation] = useState(null); + const [translationError, setTranslationError] = useState(null); + const [isTranslating, setIsTranslating] = useState(false); const imageContainerRef = useRef(null); const imageRef = useRef(null); const menuRef = useRef(null); + const translationRequestId = useRef(0); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const { showToast } = useToast(); const handleSelect = useCallback( @@ -381,6 +417,67 @@ export const TimelineItem = ({ } }, [menuOpen, account, api, displayStatus.id]); + const canTranslate = Boolean(displayStatus.content || displayStatus.htmlContent); + const isTranslationVisible = Boolean(translation); + + const handleTranslate = useCallback(() => { + if (!account) { + return; + } + setMenuOpen(false); + + if (!canTranslate) { + showToast("번역할 내용이 없습니다.", { tone: "error" }); + return; + } + + if (isTranslationVisible && !isTranslating) { + setTranslation(null); + setTranslationError(null); + return; + } + + if (isTranslating) { + return; + } + + const requestId = translationRequestId.current + 1; + translationRequestId.current = requestId; + setIsTranslating(true); + setTranslationError(null); + + void (async () => { + try { + const result = await api.translateStatus(account, displayStatus.id); + if (translationRequestId.current !== requestId) { + return; + } + setTranslation(result); + showToast("번역이 완료되었습니다.", { tone: "success" }); + } catch (error) { + if (translationRequestId.current !== requestId) { + return; + } + console.error("번역 실패:", error); + setTranslation(null); + setTranslationError("번역에 실패했습니다."); + showToast("번역에 실패했습니다.", { tone: "error" }); + } finally { + if (translationRequestId.current === requestId) { + setIsTranslating(false); + } + } + })(); + }, [ + account, + api, + canTranslate, + displayStatus.id, + isTranslating, + isTranslationVisible, + showToast + ]); + // useImageZoom 사용 const { zoom: imageZoom, @@ -439,6 +536,13 @@ export const TimelineItem = ({ useEffect(() => { setPreviewCard(displayStatus.card ?? null); }, [displayStatus.card, displayStatus.id]); + + useEffect(() => { + translationRequestId.current += 1; + setTranslation(null); + setTranslationError(null); + setIsTranslating(false); + }, [displayStatus.id]); const displayHandle = useMemo(() => { if (displayStatus.accountHandle.includes("@")) { return displayStatus.accountHandle; @@ -947,99 +1051,121 @@ export const TimelineItem = ({ return tokens; }, []); + const renderContentParts = useCallback( + (content: string, htmlContent: string | null | undefined, hasRichContent: boolean, keyPrefix: string) => { + const hasHtmlTags = htmlContent ? /<[^>]+>/g.test(htmlContent) : false; - const contentParts = useMemo(() => { - // Check if content actually contains HTML tags before rendering as HTML - const hasHtmlTags = displayStatus.htmlContent ? /<[^>]+>/g.test(displayStatus.htmlContent) : false; - - if (displayStatus.hasRichContent && hasHtmlTags) { - // Process HTML content to ensure custom emojis are properly rendered - let processedHtml = displayStatus.htmlContent || ''; - - // If custom emojis should be shown and we have emoji data, replace any remaining shortcodes - if (showCustomEmojis && displayStatus.customEmojis.length > 0) { - const emojiMap = buildEmojiMap(displayStatus.customEmojis); - - // Replace any remaining :shortcode: patterns that weren't converted to tags - processedHtml = processedHtml.replace(/:([a-zA-Z0-9_]+):/g, (match, shortcode) => { - const url = emojiMap.get(shortcode); - if (url) { - return `:${shortcode}:`; - } - return match; // Keep original if no emoji found - }); - } - - return ( -
- ); - } - - if (account?.platform === "misskey") { - const emojiMap = - showCustomEmojis && displayStatus.customEmojis.length > 0 - ? buildEmojiMap(displayStatus.customEmojis) - : undefined; - const markdownHtml = renderMarkdown(displayStatus.content, emojiMap, { mentionResolver: resolveMentionUrl }); - return ( -
- ); - } + if (hasRichContent && hasHtmlTags) { + let processedHtml = htmlContent || ""; - // Fallback to plain text with link detection - const text = displayStatus.content; - if (!showCustomEmojis || displayStatus.customEmojis.length === 0) { - return renderTextWithLinks(text, "content", { - mentionResolver: resolveMention, - onMentionClick: handleMentionClick - }); - } - const emojiMap = buildEmojiMap(displayStatus.customEmojis); - const tokens = tokenizeWithEmojis(text, emojiMap); - const parts: React.ReactNode[] = []; - tokens.forEach((token, index) => { - if (token.type === "text") { - parts.push( - ...renderTextWithLinks(token.value, `content-${index}`, { - mentionResolver: resolveMention, - onMentionClick: handleMentionClick - }) + if (showCustomEmojis && displayStatus.customEmojis.length > 0) { + const emojiMap = buildEmojiMap(displayStatus.customEmojis); + processedHtml = processedHtml.replace(/:([a-zA-Z0-9_]+):/g, (match, shortcode) => { + const url = emojiMap.get(shortcode); + if (url) { + return `:${shortcode}:`; + } + return match; + }); + } + + return ( +
); - } else { - parts.push( - {`:${token.name}:`} 0 + ? buildEmojiMap(displayStatus.customEmojis) + : undefined; + const markdownHtml = renderMarkdown(content, emojiMap, { mentionResolver: resolveMentionUrl }); + return ( +
); } - }); - return parts; - }, [ - buildEmojiMap, - account?.platform, - displayStatus.content, - displayStatus.customEmojis, - displayStatus.hasRichContent, - displayStatus.htmlContent, - handleMentionClick, - handleRichContentClick, - resolveMention, - resolveMentionUrl, - showCustomEmojis, - tokenizeWithEmojis - ]); + + if (!showCustomEmojis || displayStatus.customEmojis.length === 0) { + return renderTextWithLinks(content, keyPrefix, { + mentionResolver: resolveMention, + onMentionClick: handleMentionClick + }); + } + const emojiMap = buildEmojiMap(displayStatus.customEmojis); + const tokens = tokenizeWithEmojis(content, emojiMap); + const parts: React.ReactNode[] = []; + tokens.forEach((token, index) => { + if (token.type === "text") { + parts.push( + ...renderTextWithLinks(token.value, `${keyPrefix}-${index}`, { + mentionResolver: resolveMention, + onMentionClick: handleMentionClick + }) + ); + } else { + parts.push( + {`:${token.name}:`} + ); + } + }); + return parts; + }, + [ + account?.platform, + buildEmojiMap, + displayStatus.customEmojis, + handleMentionClick, + handleRichContentClick, + resolveMention, + resolveMentionUrl, + showCustomEmojis, + tokenizeWithEmojis + ] + ); + + const contentParts = useMemo( + () => renderContentParts(displayStatus.content, displayStatus.htmlContent, displayStatus.hasRichContent, "content"), + [displayStatus.content, displayStatus.hasRichContent, displayStatus.htmlContent, renderContentParts] + ); + + const translationParts = useMemo(() => { + if (!translation) { + return null; + } + const hasRichContent = Boolean(translation.htmlContent); + return renderContentParts(translation.content, translation.htmlContent, hasRichContent, "translation"); + }, [renderContentParts, translation]); + + const translationMeta = useMemo(() => { + if (!translation) { + return null; + } + const items: string[] = []; + if (translation.sourceLanguage) { + items.push(`감지 언어: ${translation.sourceLanguage}`); + } + if (translation.targetLanguage) { + items.push(`번역 대상: ${translation.targetLanguage}`); + } + if (translation.provider) { + items.push(`제공: ${translation.provider}`); + } + return items.length > 0 ? items : null; + }, [translation]); const accountLabel = displayStatus.accountName || displayStatus.accountHandle; const accountNameNode = useMemo(() => { @@ -1209,14 +1335,16 @@ export const TimelineItem = ({ const data = (await response.json()) as | { url?: string; title?: string; description?: string | null; image?: string | null; error?: string } | undefined; - if (!data || data.error || !data.title || !data.url) { + const title = normalizePreviewText(data?.title); + const description = normalizePreviewText(data?.description); + if (!data || data.error || !title || !data.url) { previewCache.set(previewCandidate, null); return; } const card: LinkCard = { url: data.url, - title: data.title, - description: data.description ?? null, + title, + description, image: data.image ?? null }; previewCache.set(previewCandidate, card); @@ -1386,24 +1514,38 @@ export const TimelineItem = ({ setMenuOpen(false); void (async () => { + const previousState = favouriteState; + if (previousState === null) { + return; + } + const nextState = !previousState; try { - if (favouriteState) { + if (previousState) { await api.unfavourite(account, displayStatus.id); } else { await api.favourite(account, displayStatus.id); } - - const state = await api.fetchNoteState(account, displayStatus.id); - const newFavouriteState = state.isFavourited; - setFavouriteState(newFavouriteState); - showToast( - newFavouriteState ? "즐겨찾기에 추가했습니다." : "즐겨찾기에서 해제했습니다.", - { tone: "success" } - ); + setFavouriteState(nextState); } catch (error) { console.error("즐겨찾기 처리 실패:", error); + setFavouriteState(previousState); showToast("즐겨찾기 처리에 실패했습니다.", { tone: "error" }); + return; + } + let finalState = nextState; + try { + const state = await api.fetchNoteState(account, displayStatus.id); + if (state.isFavourited !== nextState) { + setFavouriteState(state.isFavourited); + finalState = state.isFavourited; + } + } catch (error) { + console.error("즐겨찾기 상태 확인 실패:", error); } + showToast( + finalState ? "즐겨찾기에 추가했습니다." : "즐겨찾기에서 해제했습니다.", + { tone: "success" } + ); })(); }} > @@ -1421,6 +1563,21 @@ export const TimelineItem = ({ {displayStatus.bookmarked ? "북마크 취소" : "북마크"} )} + {account ? ( + + ) : null} @@ -1439,7 +1596,35 @@ export const TimelineItem = ({ ) : null} {showContent ? ( <> -

{displayStatus.content ? contentParts : "(내용 없음)"}

+
{displayStatus.content ? contentParts : "(내용 없음)"}
+ {translation ? ( +
+
+ 번역 + +
+
+ {translationParts ?? "(내용 없음)"} +
+ {translationMeta ? ( +
+ {translationMeta.map((item) => ( + {item} + ))} +
+ ) : null} +
+ ) : null} {previewCard ? ( void; onRemoveSection: (sectionId: string) => void; onReply: (status: Status, account: Account | null) => void; - onStatusClick: (status: Status, columnAccount: Account | null) => void; + onStatusClick: (status: Status, columnAccount: Account | null, settings: SectionDisplaySettings) => void; onReact: (account: Account | null, status: Status, reaction: ReactionInput) => void; - onProfileClick: (status: Status, account: Account | null) => void; + onProfileClick: (status: Status, account: Account | null, settings: SectionDisplaySettings) => void; onError: (message: string | null) => void; onMoveSection: (sectionId: string, direction: "left" | "right") => void; onScrollToSection: (sectionId: string) => void; onCloseStatusModal: () => void; onTimelineItemsChange: (sectionId: string, items: Status[]) => void; onSelectStatus: (sectionId: string, statusId: string) => void; + onUpdateSectionSettings: (sectionId: string, updates: Partial) => void; canMoveLeft: boolean; canMoveRight: boolean; canRemoveSection: boolean; timelineType: TimelineType; - showProfileImage: boolean; - showCustomEmojis: boolean; - showReactions: boolean; registerTimelineListener: (accountId: string, listener: (status: Status) => void) => void; unregisterTimelineListener: (accountId: string, listener: (status: Status) => void) => void; registerTimelineShortcutHandler: (sectionId: string, handler: ((event: KeyboardEvent) => boolean) | null) => void; @@ -45,6 +49,12 @@ type TimelineSectionProps = { selectedStatusId: string | null; }; +const SECTION_SIZE_MAP: Record = { + small: { width: 440, maxWidth: 520 }, + medium: { width: 550, maxWidth: 650 }, + large: { width: 660, maxWidth: 780 } +}; + const TimelineIcon = ({ timeline }: { timeline: TimelineType | string }) => { switch (timeline) { case "divider-before-bookmarks": @@ -133,12 +143,10 @@ export const TimelineSection = ({ canMoveRight, canRemoveSection, timelineType, - showProfileImage, - showCustomEmojis, - showReactions, registerTimelineListener, unregisterTimelineListener, registerTimelineShortcutHandler, + onUpdateSectionSettings, columnRef, selectedStatusId }: TimelineSectionProps) => { @@ -160,6 +168,7 @@ export const TimelineSection = ({ const menuRef = useRef(null); const timelineMenuRef = useRef(null); const notificationMenuRef = useRef(null); + const settingsPanelRef = useRef(null); const scrollRef = useRef(null); const notificationScrollRef = useRef(null); const accountSummaryRef = useRef(null); @@ -167,11 +176,21 @@ export const TimelineSection = ({ const [menuOpen, setMenuOpen] = useState(false); const [timelineMenuOpen, setTimelineMenuOpen] = useState(false); const [notificationsOpen, setNotificationsOpen] = useState(false); + const [settingsOpen, setSettingsOpen] = useState(false); const [notificationCount, setNotificationCount] = useState(0); const [isAtTop, setIsAtTop] = useState(true); const [highlightedTimelineIndex, setHighlightedTimelineIndex] = useState(null); const [highlightedSectionMenuIndex, setHighlightedSectionMenuIndex] = useState(null); const [highlightedNotificationIndex, setHighlightedNotificationIndex] = useState(null); + const { showProfileImages, showCustomEmojis, showReactions, sectionSize } = section.settings; + const settingsIdPrefix = `section-settings-${section.id}`; + const columnStyle = useMemo(() => { + const sizeConfig = SECTION_SIZE_MAP[sectionSize]; + return { + "--timeline-column-width": `${sizeConfig.width}px`, + "--timeline-column-max-width": `${sizeConfig.maxWidth}px` + } as React.CSSProperties; + }, [sectionSize]); const { showToast } = useToast(); const timelineOptions = useMemo(() => getTimelineOptions(account?.platform, false), [account?.platform]); const actionableTimelineOptions = useMemo( @@ -222,7 +241,8 @@ export const TimelineSection = ({ api: services.api, streaming: services.streaming, timelineType, - onNotification: handleNotification + onNotification: handleNotification, + pauseUpdates: !isAtTop }); const actionsDisabled = timelineType === "notifications" || timelineType === "bookmarks"; const emptyMessage = timelineType === "notifications" @@ -230,6 +250,9 @@ export const TimelineSection = ({ : timelineType === "bookmarks" ? "북마크한 글이 없습니다." : "표시할 글이 없습니다."; + const pendingCount = timeline.pendingCount ?? 0; + const pendingCountLabel = pendingCount >= 99 ? "99+" : String(pendingCount); + const hasPendingUpdates = pendingCount > 0 && !isAtTop; useEffect(() => { onTimelineItemsChange(section.id, timeline.items); @@ -277,6 +300,8 @@ export const TimelineSection = ({ useClickOutside(notificationMenuRef, notificationsOpen, () => setNotificationsOpen(false)); + useClickOutside(settingsPanelRef, settingsOpen, () => setSettingsOpen(false)); + useEffect(() => { if (!timelineMenuOpen) { setHighlightedTimelineIndex(null); @@ -459,12 +484,18 @@ export const TimelineSection = ({ } }; - const scrollToTop = () => { + const scrollToTop = (behavior: ScrollBehavior = "smooth") => { if (scrollRef.current) { - scrollRef.current.scrollTo({ top: 0, behavior: "smooth" }); + scrollRef.current.scrollTo({ top: 0, behavior }); } }; + const handleShowPending = () => { + timeline.flushPending(); + setIsAtTop(true); + scrollToTop("auto"); + }; + const handleOpenInstanceOrigin = useCallback(() => { if (!instanceOriginUrl) { return; @@ -488,6 +519,11 @@ export const TimelineSection = ({ setMenuOpen(false); return true; } + if (settingsOpen) { + event.preventDefault(); + setSettingsOpen(false); + return true; + } if (notificationsOpen) { event.preventDefault(); setNotificationsOpen(false); @@ -568,7 +604,7 @@ export const TimelineSection = ({ const status = notificationItems[highlightedNotificationIndex]; if (status) { event.preventDefault(); - onStatusClick(status, account); + onStatusClick(status, account, section.settings); return true; } return true; @@ -660,12 +696,12 @@ export const TimelineSection = ({ } if (key === "enter") { event.preventDefault(); - onStatusClick(selectedStatus, account); + onStatusClick(selectedStatus, account, section.settings); return true; } if (key === "p") { event.preventDefault(); - onProfileClick(selectedStatus, account); + onProfileClick(selectedStatus, account, section.settings); return true; } if (key === "a") { @@ -693,6 +729,7 @@ export const TimelineSection = ({ setTimelineMenuOpen(true); setMenuOpen(false); setNotificationsOpen(false); + setSettingsOpen(false); return true; } if (key === "g") { @@ -704,6 +741,7 @@ export const TimelineSection = ({ setNotificationsOpen((current) => !current); setTimelineMenuOpen(false); setMenuOpen(false); + setSettingsOpen(false); return true; } if (key === "m") { @@ -711,6 +749,7 @@ export const TimelineSection = ({ setMenuOpen(true); setTimelineMenuOpen(false); setNotificationsOpen(false); + setSettingsOpen(false); return true; } return false; @@ -732,6 +771,8 @@ export const TimelineSection = ({ onStatusClick, onTimelineChange, section.id, + section.settings, + settingsOpen, selectedStatusId, showReactions, timeline.items, @@ -740,7 +781,7 @@ export const TimelineSection = ({ ); useEffect(() => { - if (!timelineMenuOpen && !notificationsOpen && !menuOpen) { + if (!timelineMenuOpen && !notificationsOpen && !menuOpen && !settingsOpen) { return; } const onKeyDown = (event: KeyboardEvent) => { @@ -751,7 +792,7 @@ export const TimelineSection = ({ }; window.addEventListener("keydown", onKeyDown, { capture: true }); return () => window.removeEventListener("keydown", onKeyDown, { capture: true }); - }, [handleTimelineShortcuts, menuOpen, notificationsOpen, timelineMenuOpen]); + }, [handleTimelineShortcuts, menuOpen, notificationsOpen, settingsOpen, timelineMenuOpen]); useEffect(() => { registerTimelineShortcutHandler(section.id, handleTimelineShortcuts); @@ -763,6 +804,7 @@ export const TimelineSection = ({ className="timeline-column" ref={columnRef} data-section-id={section.id} + style={columnStyle} >
!current); setMenuOpen(false); setNotificationsOpen(false); + setSettingsOpen(false); }} disabled={!account} aria-label={timelineButtonLabel} @@ -853,6 +896,7 @@ export const TimelineSection = ({ setMenuOpen(false); setTimelineMenuOpen(false); setNotificationsOpen((current) => !current); + setSettingsOpen(false); }} disabled={!account} aria-label={notificationBadgeLabel} @@ -887,13 +931,13 @@ export const TimelineSection = ({ key={status.id} status={status} onReply={(item) => onReply(item, account)} - onStatusClick={(currentStatus) => onStatusClick(currentStatus, account)} + onStatusClick={(currentStatus) => onStatusClick(currentStatus, account, section.settings)} onToggleFavourite={handleToggleFavourite} onToggleReblog={handleToggleReblog} onToggleBookmark={handleToggleBookmark} onDelete={handleDeleteStatus} onReact={handleReact} - onProfileClick={(item) => onProfileClick(item, account)} + onProfileClick={(item) => onProfileClick(item, account, section.settings)} activeHandle={ account?.handle ? formatHandle(account.handle, account.instanceUrl) : account?.instanceUrl ?? "" } @@ -901,7 +945,7 @@ export const TimelineSection = ({ activeAccountUrl={account?.url ?? null} account={account} api={services.api} - showProfileImage={showProfileImage} + showProfileImage={showProfileImages} showCustomEmojis={showCustomEmojis} showReactions={showReactions} disableActions @@ -926,6 +970,7 @@ export const TimelineSection = ({ setMenuOpen((current) => !current); setNotificationsOpen(false); setTimelineMenuOpen(false); + setSettingsOpen(false); }} >
; + } + const icon = (() => { + switch (item.label) { + case "새로고침": + return ( + + ); + case "원본 서버에서 보기": + return ( + + ); + case "섹션 설정": + return ( + + ); + case "왼쪽 섹션 추가": + return null; + case "왼쪽으로 이동": + return null; + case "오른쪽으로 이동": + return null; + case "오른쪽 섹션 추가": + return null; + case "섹션 삭제": + return null; + default: + return null; + } + })(); const className = [ item.danger ? "danger" : "", highlightedSectionMenuIndex === index ? "is-highlighted" : "" @@ -1011,17 +1112,129 @@ export const TimelineSection = ({ onClick={item.onClick} disabled={item.disabled} > - {item.label} + {icon ? ( + + ) : null} + {item.label} ); })}
) : null} + {settingsOpen ? ( + <> +
+ {hasPendingUpdates ? ( + <> + + 새 글 {pendingCountLabel}개가 새로 도착했습니다. + + + + ) : null} {!account ?

계정을 선택하면 타임라인을 불러옵니다.

: null} {account && timeline.items.length === 0 && !timeline.loading ? (

{emptyMessage}

@@ -1033,7 +1246,7 @@ export const TimelineSection = ({ key={status.id} status={status} onReply={(item) => onReply(item, account)} - onStatusClick={(currentStatus) => onStatusClick(currentStatus, account)} + onStatusClick={(currentStatus) => onStatusClick(currentStatus, account, section.settings)} onSelect={(statusId) => onSelectStatus(section.id, statusId)} isSelected={selectedStatusId === status.id} onUpdateStatus={timeline.updateItem} @@ -1042,7 +1255,7 @@ export const TimelineSection = ({ onToggleBookmark={handleToggleBookmark} onDelete={handleDeleteStatus} onReact={handleReact} - onProfileClick={(item) => onProfileClick(item, account)} + onProfileClick={(item) => onProfileClick(item, account, section.settings)} activeHandle={ account?.handle ? formatHandle(account.handle, account.instanceUrl) : account?.instanceUrl ?? "" } @@ -1050,7 +1263,7 @@ export const TimelineSection = ({ activeAccountUrl={account?.url ?? null} account={account} api={services.api} - showProfileImage={showProfileImage} + showProfileImage={showProfileImages} showCustomEmojis={showCustomEmojis} showReactions={showReactions} disableActions={actionsDisabled} @@ -1063,7 +1276,7 @@ export const TimelineSection = ({