diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index deb951f..e31d590 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,7 +19,8 @@ "customizations": { "vscode": { "extensions": [ - "justjavac.vscode-deno-extensionpack" + "justjavac.vscode-deno-extensionpack", + "mrorz.language-gettext" ] } }, diff --git a/deno.json b/deno.json index e89a09c..342f044 100644 --- a/deno.json +++ b/deno.json @@ -3,7 +3,9 @@ "dev": "deno run --env-file -A npm:vite & deno run -A server:start", "build": "deno run --env-file -A npm:vite build", "server:start": "deno run --env-file -A --watch api/main.ts", - "serve": "deno run build && deno run -A server:start" + "serve": "deno task build && deno run -A server:start", + "i18n:extract": "deno run -A scripts/lingui-extract.ts", + "i18n:compile": "lingui compile" }, "nodeModulesDir": "auto", "compilerOptions": { diff --git a/deno.lock b/deno.lock index a032679..d1556fa 100644 --- a/deno.lock +++ b/deno.lock @@ -20,12 +20,19 @@ "jsr:@std/path@1.0": "1.0.9", "jsr:@std/path@^1.1.4": "1.1.4", "jsr:@tajpouria/cors@^1.2.1": "1.2.1", - "npm:@deno/vite-plugin@^1.0.6": "1.0.6_vite@8.0.1__@types+node@24.12.0_@types+node@24.12.0", "npm:@eslint/js@^9.39.4": "9.39.4", + "npm:@lingui/cli@*": "5.9.4_typescript@5.9.3", + "npm:@lingui/cli@6.0.0-next.2": "6.0.0-next.2", + "npm:@lingui/conf@6.0.0-next.2": "6.0.0-next.2", + "npm:@lingui/core@6.0.0-next.2": "6.0.0-next.2", + "npm:@lingui/format-po@6.0.0-next.2": "6.0.0-next.2", + "npm:@lingui/react@6.0.0-next.2": "6.0.0-next.2_react@19.2.4", + "npm:@lingui/swc-plugin@6.0.0-next.2": "6.0.0-next.2_@lingui+core@6.0.0-next.2", + "npm:@lingui/vite-plugin@6.0.0-next.2": "6.0.0-next.2_vite@8.0.1__@types+node@24.12.0", "npm:@types/node@^24.12.0": "24.12.0", "npm:@types/react-dom@^19.2.3": "19.2.3_@types+react@19.2.14", "npm:@types/react@^19.2.14": "19.2.14", - "npm:@vitejs/plugin-react@^6.0.1": "6.0.1_vite@8.0.1__@types+node@24.12.0_@types+node@24.12.0", + "npm:@vitejs/plugin-react-swc@^4.3.0": "4.3.0_vite@8.0.1__@types+node@24.12.0", "npm:eslint-plugin-react-hooks@^7.0.1": "7.0.1_eslint@9.39.4", "npm:eslint-plugin-react-refresh@~0.5.2": "0.5.2_eslint@9.39.4", "npm:eslint@^9.39.4": "9.39.4", @@ -183,7 +190,7 @@ "@babel/compat-data", "@babel/helper-validator-option", "browserslist", - "lru-cache", + "lru-cache@5.1.1", "semver@6.3.1" ] }, @@ -229,6 +236,9 @@ ], "bin": true }, + "@babel/runtime@7.29.2": { + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==" + }, "@babel/template@7.28.6": { "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dependencies": [ @@ -256,11 +266,8 @@ "@babel/helper-validator-identifier" ] }, - "@deno/vite-plugin@1.0.6_vite@8.0.1__@types+node@24.12.0_@types+node@24.12.0": { - "integrity": "sha512-Sh5XqvFuKAwjARTesi0n6xRpEXm1V0UeqKh+SxIrexCofxOaieNDMqXZD02RiZCg0mrJ43V8eCMuVrDfq6mLmg==", - "dependencies": [ - "vite" - ] + "@colors/colors@1.5.0": { + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" }, "@emnapi/core@1.9.1": { "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", @@ -281,6 +288,136 @@ "tslib" ] }, + "@esbuild/aix-ppc64@0.25.12": { + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.25.12": { + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.25.12": { + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.25.12": { + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.25.12": { + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.25.12": { + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.25.12": { + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.25.12": { + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.25.12": { + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.25.12": { + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.25.12": { + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.25.12": { + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.25.12": { + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.25.12": { + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.25.12": { + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.25.12": { + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.25.12": { + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.25.12": { + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.25.12": { + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.25.12": { + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.25.12": { + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.25.12": { + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.25.12": { + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.25.12": { + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.25.12": { + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.25.12": { + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "os": ["win32"], + "cpu": ["x64"] + }, "@eslint-community/eslint-utils@4.9.1_eslint@9.39.4": { "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dependencies": [ @@ -354,6 +491,26 @@ "@humanwhocodes/retry@0.4.3": { "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" }, + "@isaacs/cliui@9.0.0": { + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==" + }, + "@jest/schemas@29.6.3": { + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": [ + "@sinclair/typebox" + ] + }, + "@jest/types@29.6.3": { + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dependencies": [ + "@jest/schemas", + "@types/istanbul-lib-coverage", + "@types/istanbul-reports", + "@types/node", + "@types/yargs", + "chalk@4.1.2" + ] + }, "@jridgewell/gen-mapping@0.3.13": { "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dependencies": [ @@ -381,6 +538,193 @@ "@jridgewell/sourcemap-codec" ] }, + "@lingui/babel-plugin-extract-messages@5.9.4": { + "integrity": "sha512-sFH5lufIBCOLwjM2hyByMIi7gaGjAPhU7md8XMQYgcEjUVtzjBQvZ9APGDdDQ5BB8xRDyqF2kvaJpJvWZu19zA==" + }, + "@lingui/babel-plugin-extract-messages@6.0.0-next.2": { + "integrity": "sha512-lefaO/jHaJabVODRZwBIIXrwFV01kQvsSDBuEQfA0BhNXhYW61iFot//4dsGQ3FBiKXiskjQbcSFHdaBNT4YBw==" + }, + "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3": { + "integrity": "sha512-Gj+H48MQWY6rV40TBVG7U91/KETznbXOJpJsf8U4merBRPZgOMCy6VuWZGy1i+YJZJF/LiberlsCCEiiPbBRqg==", + "dependencies": [ + "@babel/core", + "@babel/runtime", + "@babel/types", + "@lingui/conf@5.9.4_typescript@5.9.3", + "@lingui/core@5.9.4_@lingui+babel-plugin-lingui-macro@5.9.4__typescript@5.9.3", + "@lingui/message-utils@5.9.4" + ] + }, + "@lingui/babel-plugin-lingui-macro@6.0.0-next.2": { + "integrity": "sha512-aWrHmo6pGEBH6vO8/AClkIQ6hSyzwROz+sO1gTBIJnwqj81VgSzyTvbCWpYW5fXCrB8yQiPbyxqD40kSsaJnzQ==", + "dependencies": [ + "@babel/core", + "@babel/types", + "@lingui/conf@6.0.0-next.2", + "@lingui/message-utils@6.0.0-next.2" + ] + }, + "@lingui/cli@5.9.4_typescript@5.9.3": { + "integrity": "sha512-0QAsZCWu6PZhxYmeQfoa6cJbNRRsTkeNQ1jTow/GzBYpFlO9iXw8dCG5cBh5pHHjzjoX3koxiKyUTFyLBmKNiQ==", + "dependencies": [ + "@babel/core", + "@babel/generator", + "@babel/parser", + "@babel/runtime", + "@babel/types", + "@lingui/babel-plugin-extract-messages@5.9.4", + "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3", + "@lingui/conf@5.9.4_typescript@5.9.3", + "@lingui/core@5.9.4_@lingui+babel-plugin-lingui-macro@5.9.4__typescript@5.9.3", + "@lingui/format-po@5.9.4_typescript@5.9.3", + "@lingui/message-utils@5.9.4", + "chokidar@3.5.1", + "cli-table", + "commander@10.0.1", + "convert-source-map", + "date-fns", + "esbuild", + "glob", + "micromatch", + "ms", + "normalize-path", + "ora@5.4.1", + "picocolors", + "pofile", + "pseudolocale", + "source-map", + "threads" + ], + "bin": true + }, + "@lingui/cli@6.0.0-next.2": { + "integrity": "sha512-nZj7EaLUC4YJqgR8GJDOq1hGjwxuDoVGRd6C3qXlJY/yaPDY333CIW9VXFm2RSGozFlnppTOp48ESZo1k0mRZA==", + "dependencies": [ + "@babel/core", + "@babel/generator", + "@babel/parser", + "@babel/types", + "@lingui/babel-plugin-extract-messages@6.0.0-next.2", + "@lingui/babel-plugin-lingui-macro@6.0.0-next.2", + "@lingui/conf@6.0.0-next.2", + "@lingui/core@6.0.0-next.2", + "@lingui/format-po@6.0.0-next.2", + "@lingui/message-utils@6.0.0-next.2", + "chokidar@5.0.0", + "cli-table3", + "commander@14.0.3", + "esbuild", + "jiti", + "micromatch", + "ms", + "normalize-path", + "ora@9.3.0", + "pseudolocale", + "source-map", + "tinypool" + ], + "bin": true + }, + "@lingui/conf@5.9.4_typescript@5.9.3": { + "integrity": "sha512-crF3AQgYXg52Caz4ffJKSTXWUU/4iOGOBRnSeOkw8lsOtOYlPTaWxeSGyDTEwaGCFl6P/1aew+pvHOSCxOAyrg==", + "dependencies": [ + "@babel/runtime", + "cosmiconfig", + "jest-validate", + "jiti", + "picocolors" + ] + }, + "@lingui/conf@6.0.0-next.2": { + "integrity": "sha512-DZGvd0yVOP6bhG+wZei0nPIHiyO3LlgG/u/DFFyeCPT+hr4j9hFRH6efsN5hRgCZ4qf8sxiKWwvOAhfLIcbKsQ==", + "dependencies": [ + "jest-validate", + "jiti", + "lilconfig", + "normalize-path" + ] + }, + "@lingui/core@5.9.4_@lingui+babel-plugin-lingui-macro@5.9.4__typescript@5.9.3": { + "integrity": "sha512-MsYYc8ue/w1C8bgAbC3h4cNik64bqZ6xGxMjsVdoGQBUe+b/ij+rOEiuJXbwvlo4GXBsvsan7EzeH7sx11IsYQ==", + "dependencies": [ + "@babel/runtime", + "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3", + "@lingui/message-utils@5.9.4" + ], + "optionalPeers": [ + "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3" + ] + }, + "@lingui/core@6.0.0-next.2": { + "integrity": "sha512-JuGvuIlfW4HyCK2cvagqN9o5AG9TwJQ9wP36scxtobufFC0wNCxh7lC5sfsYz+KJwY62duCdYEiXfbMhBvmW1Q==", + "dependencies": [ + "@lingui/babel-plugin-lingui-macro@6.0.0-next.2", + "@lingui/message-utils@6.0.0-next.2" + ] + }, + "@lingui/format-po@5.9.4_typescript@5.9.3": { + "integrity": "sha512-B+e8YF6S5EOUPF6i3gaSX69pPs/QkP6MIE97vYA48W9Lty7KFOHuYBk/YzCY9CSQaF7gW3GAI5ZsXX2+ZLVyZw==", + "dependencies": [ + "@lingui/conf@5.9.4_typescript@5.9.3", + "@lingui/message-utils@5.9.4", + "date-fns", + "pofile" + ] + }, + "@lingui/format-po@6.0.0-next.2": { + "integrity": "sha512-vlQCy0tFYKGLT2VS8rlYjh36CBAcQAdzezo6IphtIeg5E3jQYWukrVsAVKqurUHe3/kKVNyyWEMODeH84pIJiA==", + "dependencies": [ + "@lingui/conf@6.0.0-next.2", + "@lingui/message-utils@6.0.0-next.2", + "pofile" + ] + }, + "@lingui/message-utils@5.9.4": { + "integrity": "sha512-YzAVzILdsqdUqwHmryl7rfwZXRHYs6QY2wPLH5gxrV7wlieiCaskaKPeSk2SuN/gmC8im1GDrQHcwgKapFU3Sg==", + "dependencies": [ + "@messageformat/parser", + "js-sha256" + ] + }, + "@lingui/message-utils@6.0.0-next.2": { + "integrity": "sha512-3vI2La2XLD/mrsdkghMmuuHuCS9+pvGXOKofCNjDqiayvCPV9q5YeOGa3jb+frOLfIuJ/IiHplNO4wIq3y2kuw==", + "dependencies": [ + "@messageformat/date-skeleton", + "@messageformat/parser", + "js-sha256" + ] + }, + "@lingui/react@6.0.0-next.2_react@19.2.4": { + "integrity": "sha512-EdzP+8a2UDLwzWPDG2+ctoFrU4oAyWFvijimTm6m6o39s/WHci+yH/eD7tNFr+bOUaynwoTI7azxfJJzbaDZNQ==", + "dependencies": [ + "@lingui/babel-plugin-lingui-macro@6.0.0-next.2", + "@lingui/core@6.0.0-next.2", + "react" + ] + }, + "@lingui/swc-plugin@6.0.0-next.2_@lingui+core@6.0.0-next.2": { + "integrity": "sha512-rD20Y7gpquPUw7e/V33lOcsTa28kuuP2GpIK74iQ46KFWm067DKNUkGfcfeEHG6ORBJck6W+KvqeOZ/B4vID1Q==", + "dependencies": [ + "@lingui/core@6.0.0-next.2" + ] + }, + "@lingui/vite-plugin@6.0.0-next.2_vite@8.0.1__@types+node@24.12.0": { + "integrity": "sha512-dK+imM95tZ7LoJoh8VOp7P20cjgXoC2oWx9dW7zoYz40jZsvIssRYQ4a4jiYM4UNp+fMdaAo5t0GDbqIJ9EZGw==", + "dependencies": [ + "@lingui/cli@6.0.0-next.2", + "@lingui/conf@6.0.0-next.2", + "vite" + ] + }, + "@messageformat/date-skeleton@1.1.0": { + "integrity": "sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==" + }, + "@messageformat/parser@5.1.1": { + "integrity": "sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==", + "dependencies": [ + "moo" + ] + }, "@napi-rs/wasm-runtime@1.1.1": { "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "dependencies": [ @@ -475,6 +819,100 @@ "@rolldown/pluginutils@1.0.0-rc.7": { "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==" }, + "@sinclair/typebox@0.27.10": { + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==" + }, + "@swc/core-darwin-arm64@1.15.21": { + "integrity": "sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@swc/core-darwin-x64@1.15.21": { + "integrity": "sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@swc/core-linux-arm-gnueabihf@1.15.21": { + "integrity": "sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@swc/core-linux-arm64-gnu@1.15.21": { + "integrity": "sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@swc/core-linux-arm64-musl@1.15.21": { + "integrity": "sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@swc/core-linux-ppc64-gnu@1.15.21": { + "integrity": "sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@swc/core-linux-s390x-gnu@1.15.21": { + "integrity": "sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@swc/core-linux-x64-gnu@1.15.21": { + "integrity": "sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@swc/core-linux-x64-musl@1.15.21": { + "integrity": "sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@swc/core-win32-arm64-msvc@1.15.21": { + "integrity": "sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@swc/core-win32-ia32-msvc@1.15.21": { + "integrity": "sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@swc/core-win32-x64-msvc@1.15.21": { + "integrity": "sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@swc/core@1.15.21": { + "integrity": "sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==", + "dependencies": [ + "@swc/counter", + "@swc/types" + ], + "optionalDependencies": [ + "@swc/core-darwin-arm64", + "@swc/core-darwin-x64", + "@swc/core-linux-arm-gnueabihf", + "@swc/core-linux-arm64-gnu", + "@swc/core-linux-arm64-musl", + "@swc/core-linux-ppc64-gnu", + "@swc/core-linux-s390x-gnu", + "@swc/core-linux-x64-gnu", + "@swc/core-linux-x64-musl", + "@swc/core-win32-arm64-msvc", + "@swc/core-win32-ia32-msvc", + "@swc/core-win32-x64-msvc" + ], + "scripts": true + }, + "@swc/counter@0.1.3": { + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "@swc/types@0.1.26": { + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "dependencies": [ + "@swc/counter" + ] + }, "@tybys/wasm-util@0.10.1": { "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dependencies": [ @@ -502,6 +940,21 @@ "@types/unist@3.0.3" ] }, + "@types/istanbul-lib-coverage@2.0.6": { + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + }, + "@types/istanbul-lib-report@3.0.3": { + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dependencies": [ + "@types/istanbul-lib-coverage" + ] + }, + "@types/istanbul-reports@3.0.4": { + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dependencies": [ + "@types/istanbul-lib-report" + ] + }, "@types/json-schema@7.0.15": { "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, @@ -538,6 +991,15 @@ "@types/unist@3.0.3": { "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, + "@types/yargs-parser@21.0.3": { + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + }, + "@types/yargs@17.0.35": { + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dependencies": [ + "@types/yargs-parser" + ] + }, "@typescript-eslint/eslint-plugin@8.57.1_@typescript-eslint+parser@8.57.1__eslint@9.39.4__typescript@5.9.3_eslint@9.39.4_typescript@5.9.3": { "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==", "dependencies": [ @@ -639,10 +1101,11 @@ "@ungap/structured-clone@1.3.0": { "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" }, - "@vitejs/plugin-react@6.0.1_vite@8.0.1__@types+node@24.12.0_@types+node@24.12.0": { - "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "@vitejs/plugin-react-swc@4.3.0_vite@8.0.1__@types+node@24.12.0": { + "integrity": "sha512-mOkXCII839dHyAt/gpoSlm28JIVDwhZ6tnG6wJxUy2bmOx7UaPjvOyIDf3SFv5s7Eo7HVaq6kRcu6YMEzt5Z7w==", "dependencies": [ "@rolldown/pluginutils@1.0.0-rc.7", + "@swc/core", "vite" ] }, @@ -665,12 +1128,28 @@ "uri-js" ] }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-regex@6.2.2": { + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" + }, "ansi-styles@4.3.0": { "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": [ "color-convert" ] }, + "ansi-styles@5.2.0": { + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "anymatch@3.1.3": { + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": [ + "normalize-path", + "picomatch@2.3.1" + ] + }, "argparse@2.0.1": { "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, @@ -683,10 +1162,24 @@ "balanced-match@4.0.4": { "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "baseline-browser-mapping@2.10.10": { "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", "bin": true }, + "binary-extensions@2.3.0": { + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" + }, + "bl@4.1.0": { + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": [ + "buffer", + "inherits", + "readable-stream" + ] + }, "brace-expansion@1.1.12": { "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": [ @@ -700,6 +1193,12 @@ "balanced-match@4.0.4" ] }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, "browserslist@4.28.1": { "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dependencies": [ @@ -711,9 +1210,19 @@ ], "bin": true }, + "buffer@5.7.1": { + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dependencies": [ + "base64-js", + "ieee754" + ] + }, "callsites@3.1.0": { "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, + "camelcase@6.3.0": { + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + }, "caniuse-lite@1.0.30001780": { "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==" }, @@ -723,10 +1232,13 @@ "chalk@4.1.2": { "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": [ - "ansi-styles", + "ansi-styles@4.3.0", "supports-color" ] }, + "chalk@5.6.2": { + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==" + }, "character-entities-html4@2.1.0": { "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" }, @@ -739,6 +1251,63 @@ "character-reference-invalid@2.0.1": { "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" }, + "chokidar@3.5.1": { + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dependencies": [ + "anymatch", + "braces", + "glob-parent@5.1.2", + "is-binary-path", + "is-glob", + "normalize-path", + "readdirp@3.5.0" + ], + "optionalDependencies": [ + "fsevents" + ] + }, + "chokidar@5.0.0": { + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dependencies": [ + "readdirp@5.0.0" + ] + }, + "cli-cursor@3.1.0": { + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": [ + "restore-cursor@3.1.0" + ] + }, + "cli-cursor@5.0.0": { + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dependencies": [ + "restore-cursor@5.1.0" + ] + }, + "cli-spinners@2.9.2": { + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==" + }, + "cli-spinners@3.4.0": { + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==" + }, + "cli-table3@0.6.5": { + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dependencies": [ + "string-width@4.2.3" + ], + "optionalDependencies": [ + "@colors/colors" + ] + }, + "cli-table@0.3.11": { + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dependencies": [ + "colors" + ] + }, + "clone@1.0.4": { + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" + }, "color-convert@2.0.1": { "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dependencies": [ @@ -748,9 +1317,18 @@ "color-name@1.1.4": { "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "colors@1.0.3": { + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==" + }, "comma-separated-tokens@2.0.3": { "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" }, + "commander@10.0.1": { + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" + }, + "commander@14.0.3": { + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==" + }, "concat-map@0.0.1": { "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, @@ -760,6 +1338,19 @@ "cookie@1.1.1": { "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==" }, + "cosmiconfig@8.3.6_typescript@5.9.3": { + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dependencies": [ + "import-fresh", + "js-yaml", + "parse-json", + "path-type", + "typescript" + ], + "optionalPeers": [ + "typescript" + ] + }, "cross-spawn@7.0.6": { "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": [ @@ -771,6 +1362,9 @@ "csstype@3.2.3": { "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, + "date-fns@3.6.0": { + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==" + }, "debug@4.4.3": { "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": [ @@ -786,6 +1380,12 @@ "deep-is@0.1.4": { "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, + "defaults@1.0.4": { + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": [ + "clone" + ] + }, "dequal@2.0.3": { "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, @@ -801,6 +1401,48 @@ "electron-to-chromium@1.5.321": { "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==" }, + "emoji-regex@8.0.0": { + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "error-ex@1.3.4": { + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dependencies": [ + "is-arrayish" + ] + }, + "esbuild@0.25.12": { + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "optionalDependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-arm64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/openharmony-arm64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ], + "scripts": true, + "bin": true + }, "escalade@3.2.0": { "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, @@ -859,7 +1501,7 @@ "@humanwhocodes/retry", "@types/estree", "ajv", - "chalk", + "chalk@4.1.2", "cross-spawn", "debug", "escape-string-regexp@4.0.0", @@ -871,7 +1513,7 @@ "fast-deep-equal", "file-entry-cache", "find-up", - "glob-parent", + "glob-parent@6.0.2", "ignore@5.3.2", "imurmurhash", "is-glob", @@ -883,6 +1525,9 @@ ], "bin": true }, + "esm@3.2.25": { + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" + }, "espree@10.4.0": { "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dependencies": [ @@ -927,10 +1572,10 @@ "fdir@6.5.0_picomatch@4.0.3": { "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dependencies": [ - "picomatch" + "picomatch@4.0.3" ], "optionalPeers": [ - "picomatch" + "picomatch@4.0.3" ] }, "file-entry-cache@8.0.0": { @@ -939,6 +1584,12 @@ "flat-cache" ] }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, "find-up@5.0.0": { "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dependencies": [ @@ -956,6 +1607,13 @@ "flatted@3.4.2": { "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==" }, + "foreground-child@3.3.1": { + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dependencies": [ + "cross-spawn", + "signal-exit@4.1.0" + ] + }, "frimousse@0.3.0_react@19.2.4_typescript@5.9.3": { "integrity": "sha512-kO6LMoKY/cLAYEhXXtqLRaLIE6L/DagpFPrUZaLv3LsUa1/8Iza3HhwZcgN8eZ+weXnhv69eoclNUPohcCa/IQ==", "dependencies": [ @@ -974,12 +1632,34 @@ "gensync@1.0.0-beta.2": { "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, + "get-east-asian-width@1.5.0": { + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==" + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, "glob-parent@6.0.2": { "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dependencies": [ "is-glob" ] }, + "glob@11.1.0": { + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dependencies": [ + "foreground-child", + "jackspeak", + "minimatch@10.2.4", + "minipass", + "package-json-from-dist", + "path-scurry" + ], + "deprecated": true, + "bin": true + }, "globals@14.0.0": { "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" }, @@ -1027,6 +1707,9 @@ "html-url-attributes@3.0.1": { "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==" }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "ignore@5.3.2": { "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" }, @@ -1043,6 +1726,9 @@ "imurmurhash@0.1.4": { "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, + "inherits@2.0.4": { + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "inline-style-parser@0.2.7": { "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==" }, @@ -1056,12 +1742,24 @@ "is-decimal" ] }, + "is-arrayish@0.2.1": { + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-binary-path@2.1.0": { + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": [ + "binary-extensions" + ] + }, "is-decimal@2.0.1": { "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" }, "is-extglob@2.1.1": { "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, + "is-fullwidth-code-point@3.0.0": { + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "is-glob@4.0.3": { "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dependencies": [ @@ -1071,12 +1769,57 @@ "is-hexadecimal@2.0.1": { "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" }, + "is-interactive@1.0.0": { + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, + "is-interactive@2.0.0": { + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==" + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-observable@2.1.0": { + "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==" + }, "is-plain-obj@4.1.0": { "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" }, + "is-unicode-supported@0.1.0": { + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, + "is-unicode-supported@2.1.0": { + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==" + }, "isexe@2.0.0": { "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "jackspeak@4.2.3": { + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dependencies": [ + "@isaacs/cliui" + ] + }, + "jest-get-type@29.6.3": { + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==" + }, + "jest-validate@29.7.0": { + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dependencies": [ + "@jest/types", + "camelcase", + "chalk@4.1.2", + "jest-get-type", + "leven", + "pretty-format" + ] + }, + "jiti@2.6.1": { + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "bin": true + }, + "js-sha256@0.10.1": { + "integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==" + }, "js-tokens@4.0.0": { "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, @@ -1094,6 +1837,9 @@ "json-buffer@3.0.1": { "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, + "json-parse-even-better-errors@2.3.1": { + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "json-schema-traverse@0.4.1": { "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, @@ -1110,6 +1856,9 @@ "json-buffer" ] }, + "leven@3.1.0": { + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + }, "levn@0.4.1": { "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dependencies": [ @@ -1191,6 +1940,12 @@ "lightningcss-win32-x64-msvc" ] }, + "lilconfig@3.1.3": { + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==" + }, + "lines-and-columns@1.2.4": { + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "locate-path@6.0.0": { "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dependencies": [ @@ -1200,9 +1955,26 @@ "lodash.merge@4.6.2": { "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "log-symbols@4.1.0": { + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": [ + "chalk@4.1.2", + "is-unicode-supported@0.1.0" + ] + }, + "log-symbols@7.0.1": { + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dependencies": [ + "is-unicode-supported@2.1.0", + "yoctocolors" + ] + }, "longest-streak@3.1.0": { "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" }, + "lru-cache@11.2.7": { + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==" + }, "lru-cache@5.1.1": { "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dependencies": [ @@ -1622,6 +2394,19 @@ "micromark-util-types" ] }, + "micromatch@4.0.8": { + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": [ + "braces", + "picomatch@2.3.1" + ] + }, + "mimic-fn@2.1.0": { + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mimic-function@5.0.1": { + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==" + }, "minimatch@10.2.4": { "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dependencies": [ @@ -1634,6 +2419,12 @@ "brace-expansion@1.1.12" ] }, + "minipass@7.1.3": { + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" + }, + "moo@0.5.3": { + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==" + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -1650,6 +2441,24 @@ "nodemailer@8.0.4": { "integrity": "sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==" }, + "normalize-path@3.0.0": { + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "observable-fns@0.6.1": { + "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==" + }, + "onetime@5.1.2": { + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": [ + "mimic-fn" + ] + }, + "onetime@7.0.0": { + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dependencies": [ + "mimic-function" + ] + }, "optionator@0.9.4": { "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dependencies": [ @@ -1661,6 +2470,33 @@ "word-wrap" ] }, + "ora@5.4.1": { + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": [ + "bl", + "chalk@4.1.2", + "cli-cursor@3.1.0", + "cli-spinners@2.9.2", + "is-interactive@1.0.0", + "is-unicode-supported@0.1.0", + "log-symbols@4.1.0", + "strip-ansi@6.0.1", + "wcwidth" + ] + }, + "ora@9.3.0": { + "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", + "dependencies": [ + "chalk@5.6.2", + "cli-cursor@5.0.0", + "cli-spinners@3.4.0", + "is-interactive@2.0.0", + "is-unicode-supported@2.1.0", + "log-symbols@7.0.1", + "stdin-discarder", + "string-width@8.2.0" + ] + }, "p-limit@3.1.0": { "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dependencies": [ @@ -1673,6 +2509,9 @@ "p-limit" ] }, + "package-json-from-dist@1.0.1": { + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, "parent-module@1.0.1": { "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dependencies": [ @@ -1691,21 +2530,46 @@ "is-hexadecimal" ] }, + "parse-json@5.2.0": { + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": [ + "@babel/code-frame", + "error-ex", + "json-parse-even-better-errors", + "lines-and-columns" + ] + }, "path-exists@4.0.0": { "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-key@3.1.1": { "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, + "path-scurry@2.0.2": { + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dependencies": [ + "lru-cache@11.2.7", + "minipass" + ] + }, "path-to-regexp@6.3.0": { "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" }, + "path-type@4.0.0": { + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, "picomatch@4.0.3": { "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" }, + "pofile@1.1.4": { + "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==" + }, "postcss@8.5.8": { "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dependencies": [ @@ -1717,9 +2581,24 @@ "prelude-ls@1.2.1": { "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, + "pretty-format@29.7.0": { + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": [ + "@jest/schemas", + "ansi-styles@5.2.0", + "react-is" + ] + }, "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "pseudolocale@2.2.0": { + "integrity": "sha512-O+D2eU7fO9wVLqrohvt9V/9fwMadnJQ4jxwiK+LeNEqhMx8JYx4xQHkArDCJFAdPPOp/pQq6z5L37eBvAoc8jw==", + "dependencies": [ + "commander@10.0.1" + ], + "bin": true + }, "punycode@2.3.1": { "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, @@ -1730,6 +2609,9 @@ "scheduler" ] }, + "react-is@18.3.1": { + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, "react-markdown@10.1.0_@types+react@19.2.14_react@19.2.4": { "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "dependencies": [ @@ -1763,6 +2645,23 @@ "react@19.2.4": { "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==" }, + "readable-stream@3.6.2": { + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": [ + "inherits", + "string_decoder", + "util-deprecate" + ] + }, + "readdirp@3.5.0": { + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dependencies": [ + "picomatch@2.3.1" + ] + }, + "readdirp@5.0.0": { + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==" + }, "remark-gfm@4.0.1": { "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", "dependencies": [ @@ -1804,6 +2703,20 @@ "resolve-from@4.0.0": { "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "restore-cursor@3.1.0": { + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": [ + "onetime@5.1.2", + "signal-exit@3.0.7" + ] + }, + "restore-cursor@5.1.0": { + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dependencies": [ + "onetime@7.0.0", + "signal-exit@4.1.0" + ] + }, "rolldown@1.0.0-rc.10": { "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", "dependencies": [ @@ -1829,6 +2742,9 @@ ], "bin": true }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, "scheduler@0.27.0": { "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" }, @@ -1852,12 +2768,45 @@ "shebang-regex@3.0.0": { "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "signal-exit@3.0.7": { + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "signal-exit@4.1.0": { + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + }, "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, + "source-map@0.7.6": { + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==" + }, "space-separated-tokens@2.0.2": { "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" }, + "stdin-discarder@0.3.1": { + "integrity": "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==" + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": [ + "emoji-regex", + "is-fullwidth-code-point", + "strip-ansi@6.0.1" + ] + }, + "string-width@8.2.0": { + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "dependencies": [ + "get-east-asian-width", + "strip-ansi@7.2.0" + ] + }, + "string_decoder@1.3.0": { + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": [ + "safe-buffer" + ] + }, "stringify-entities@4.0.4": { "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dependencies": [ @@ -1865,6 +2814,18 @@ "character-entities-legacy" ] }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": [ + "ansi-regex@5.0.1" + ] + }, + "strip-ansi@7.2.0": { + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dependencies": [ + "ansi-regex@6.2.2" + ] + }, "strip-json-comments@3.1.1": { "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, @@ -1886,11 +2847,38 @@ "has-flag" ] }, + "threads@1.7.0": { + "integrity": "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==", + "dependencies": [ + "callsites", + "debug", + "is-observable", + "observable-fns" + ], + "optionalDependencies": [ + "tiny-worker" + ] + }, + "tiny-worker@2.3.0": { + "integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==", + "dependencies": [ + "esm" + ] + }, "tinyglobby@0.2.15": { "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dependencies": [ "fdir", - "picomatch" + "picomatch@4.0.3" + ] + }, + "tinypool@2.1.0": { + "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==" + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" ] }, "trim-lines@3.0.1": { @@ -1992,6 +2980,9 @@ "punycode" ] }, + "util-deprecate@1.0.2": { + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "vfile-message@4.0.3": { "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "dependencies": [ @@ -2011,7 +3002,7 @@ "dependencies": [ "@types/node", "lightningcss", - "picomatch", + "picomatch@4.0.3", "postcss", "rolldown", "tinyglobby" @@ -2024,6 +3015,12 @@ ], "bin": true }, + "wcwidth@1.0.1": { + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": [ + "defaults" + ] + }, "which@2.0.2": { "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": [ @@ -2040,6 +3037,9 @@ "yocto-queue@0.1.0": { "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, + "yoctocolors@2.1.2": { + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==" + }, "zod-validation-error@4.0.2_zod@4.3.6": { "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", "dependencies": [ @@ -2064,12 +3064,17 @@ ], "packageJson": { "dependencies": [ - "npm:@deno/vite-plugin@^1.0.6", "npm:@eslint/js@^9.39.4", + "npm:@lingui/cli@6.0.0-next.2", + "npm:@lingui/core@6.0.0-next.2", + "npm:@lingui/format-po@6.0.0-next.2", + "npm:@lingui/react@6.0.0-next.2", + "npm:@lingui/swc-plugin@6.0.0-next.2", + "npm:@lingui/vite-plugin@6.0.0-next.2", "npm:@types/node@^24.12.0", "npm:@types/react-dom@^19.2.3", "npm:@types/react@^19.2.14", - "npm:@vitejs/plugin-react@^6.0.1", + "npm:@vitejs/plugin-react-swc@^4.3.0", "npm:eslint-plugin-react-hooks@^7.0.1", "npm:eslint-plugin-react-refresh@~0.5.2", "npm:eslint@^9.39.4", diff --git a/eslint.config.js b/eslint.config.js index 5e6b472..05432ac 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,7 +6,7 @@ import tseslint from 'typescript-eslint' import { defineConfig, globalIgnores } from 'eslint/config' export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(['dist', 'src/locales']), { files: ['**/*.{ts,tsx}'], extends: [ @@ -19,5 +19,14 @@ export default defineConfig([ ecmaVersion: 2020, globals: globals.browser, }, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + }, }, ]) diff --git a/lingui.config.cjs b/lingui.config.cjs new file mode 100644 index 0000000..b7e99be --- /dev/null +++ b/lingui.config.cjs @@ -0,0 +1,14 @@ +const { formatter } = require("@lingui/format-po"); + +/** @type {import("@lingui/conf").LinguiConfig} */ +module.exports = { + locales: ["en", "fr"], + sourceLocale: "en", + catalogs: [ + { + path: "src/locales/{locale}", + include: ["src"], + }, + ], + format: formatter(), +}; diff --git a/package.json b/package.json index 094ef8d..ec5c0f7 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,13 @@ "preview": "vite preview" }, "dependencies": { - "@deno/vite-plugin": "^1.0.6", + "@lingui/cli": "6.0.0-next.2", + "@lingui/core": "6.0.0-next.2", + "@lingui/format-po": "6.0.0-next.2", + "@lingui/react": "6.0.0-next.2", "@types/react": "^19.2.14", - "@vitejs/plugin-react": "^6.0.1", + "@lingui/swc-plugin": "6.0.0-next.2", + "@vitejs/plugin-react-swc": "^4.3.0", "frimousse": "^0.3.0", "react": "^19.2.4", "react-dom": "^19.2.4", @@ -21,6 +25,7 @@ "remark-gfm": "^4.0.1" }, "devDependencies": { + "@lingui/vite-plugin": "6.0.0-next.2", "@eslint/js": "^9.39.4", "@types/node": "^24.12.0", "@types/react-dom": "^19.2.3", diff --git a/scripts/lingui-extract.ts b/scripts/lingui-extract.ts new file mode 100644 index 0000000..55e3f8c --- /dev/null +++ b/scripts/lingui-extract.ts @@ -0,0 +1,5 @@ +import extract from "../node_modules/@lingui/cli/dist/lingui-extract.js"; +import { getConfig } from "../node_modules/@lingui/conf/dist/index.mjs"; + +const config = getConfig({ cwd: Deno.cwd() }); +await extract(config, { verbose: false, watch: false, files: [], workersOptions: { poolSize: 0 } }); diff --git a/src/App.css b/src/App.css index c09b6f8..378d22f 100644 --- a/src/App.css +++ b/src/App.css @@ -511,6 +511,85 @@ display: block; } +.file-preview-play-btn { + position: relative; + width: 100%; + padding: 0; + border: none; + background: #000; + cursor: pointer; + display: block; + border-radius: 8px; + overflow: hidden; +} + +.file-preview-video-thumb { + width: 100%; + max-height: 480px; + object-fit: contain; + display: block; +} + +.file-preview-play-btn .rich-content-play-overlay { + font-size: 3rem; +} + +.audio-file-preview { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface) 92%); + border: 2px solid var(--color-border); + border-radius: 0 0 12px 12px; +} + +.audio-file-preview--active .waveform-bar { + fill: var(--color-accent); +} + +.audio-file-preview--active .audio-player-btn { + background: var(--color-accent-hover); +} + +.file-preview-audio-btn { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 1rem 1.25rem; + border: 2px solid var(--color-border); + border-radius: 10px; + background: var(--color-bg); + cursor: pointer; + color: var(--color-text); + width: 100%; + font-size: 0.95rem; + transition: border-color 0.15s; +} + +.file-preview-audio-btn:hover { + border-color: var(--color-accent); +} + +.file-preview-audio-icon { + font-size: 1.5rem; + flex-shrink: 0; +} + +.file-preview-audio-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.file-preview-audio-play { + flex-shrink: 0; + opacity: 0.5; +} + /* ── Video player ── */ .video-player { width: 100%; @@ -639,6 +718,42 @@ ); } +/* ── Waveform ── */ +.waveform-svg { + flex: 1; + min-width: 0; + height: 48px; + cursor: pointer; + display: block; +} + +.waveform-bar { + fill: color-mix(in srgb, var(--color-accent) 25%, var(--color-border) 75%); +} + +.waveform-bar--played { + fill: var(--color-accent); +} + +.waveform-skeleton { + flex: 1; + min-width: 0; + height: 48px; + border-radius: 3px; + background: color-mix(in srgb, var(--color-accent) 12%, var(--color-border) 88%); + position: relative; + overflow: hidden; + cursor: pointer; +} + +.waveform-skeleton-fill { + position: absolute; + inset: 0 auto 0 0; + background: var(--color-accent); + opacity: 0.45; + transition: width 0.1s linear; +} + .audio-player-track--volume { flex: 1 1 100px; max-width: 120px; @@ -824,7 +939,8 @@ .global-player--reduced .global-player-body { grid-template-rows: 0fr; } -.global-player-iframe-wrap { +.global-player-iframe-wrap, +.global-player-media-wrap { overflow: hidden; min-height: 0; border-radius: 0 0 8px 8px; @@ -1567,10 +1683,10 @@ body.has-player .fab-new { margin: 1rem auto 0 auto; max-width: 860px; padding: 0.75rem 1rem; - border: 1px solid color-mix(in srgb, var(--color-danger) 30%, transparent); + border: none; border-radius: 10px; - background: color-mix(in srgb, var(--color-danger-bg) 92%, white 8%); - color: var(--color-text); + background: var(--color-danger-bg); + color: var(--color-on-accent); line-height: 1.5; overflow-wrap: anywhere; word-break: break-word; @@ -2256,6 +2372,22 @@ body.has-player .fab-new { opacity: 0.7; } +/* Fill the 48Ɨ48 preview box and center content for media buttons */ +.dump-card-preview .rich-content-thumbnail-btn { + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +.dump-card-preview .rich-content-compact-thumbnail { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 0; + border: none; +} + /* ── Shared card body ── */ .dump-card-body, .playlist-card-body { @@ -2494,6 +2626,8 @@ body.has-player .fab-new { .modal-body { padding: 1rem 1.25rem; flex: 1 1 auto; + min-height: 0; + overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem; diff --git a/src/App.tsx b/src/App.tsx index d4eb484..cae3f5c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,65 +19,57 @@ import { AuthProvider } from "./contexts/AuthProvider.tsx"; import { PlayerProvider } from "./contexts/PlayerProvider.tsx"; import { WSProvider } from "./contexts/WSProvider.tsx"; import { FollowProvider } from "./contexts/FollowProvider.tsx"; -import { useAuth } from "./hooks/useAuth.ts"; import { GlobalPlayer } from "./components/GlobalPlayer.tsx"; import "./App.css"; function AppRoutes() { - const { token, user, logout } = useAuth(); return ( - - - - - } /> - } /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - } /> - } /> - } - /> - } /> - } /> - - - - } - /> - - - - + + } /> + } /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + } /> + } /> + } + /> + } /> + } /> + + + + } + /> + ); } @@ -85,7 +77,13 @@ function App() { return ( - + + + + + + + diff --git a/src/assets/hero.png b/src/assets/hero.png deleted file mode 100644 index cc51a3d..0000000 Binary files a/src/assets/hero.png and /dev/null differ diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 886452f..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/src/assets/vite.svg b/src/assets/vite.svg deleted file mode 100644 index 93caf61..0000000 --- a/src/assets/vite.svg +++ /dev/null @@ -1,366 +0,0 @@ - - Vite - diff --git a/src/components/AddToPlaylistModal.tsx b/src/components/AddToPlaylistModal.tsx index 4aaf71a..c7a50ed 100644 --- a/src/components/AddToPlaylistModal.tsx +++ b/src/components/AddToPlaylistModal.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { t } from "@lingui/core/macro"; import { API_URL } from "../config/api.ts"; import { useAuth } from "../hooks/useAuth.ts"; import type { PlaylistMembership, RawPlaylistMembership } from "../model.ts"; @@ -32,7 +33,7 @@ export function AddToPlaylistModal( }) .catch(() => {}) .finally(() => setLoading(false)); - }, [dumpId]); + }, [dumpId, authFetch]); const toggleMembership = async (membership: PlaylistMembership) => { const { playlist, hasDump } = membership; @@ -60,7 +61,7 @@ export function AddToPlaylistModal( }; return ( - + - Playlists + Playlists @@ -56,16 +58,16 @@ export function AppHeader( className="btn-primary" onClick={() => setCreateModalOpen(true)} disabled={disableNew} - title={disableNew ? "Server unreachable" : undefined} + title={disableNew ? t`Server unreachable` : undefined} > - + New + + New ) : ( <> )} @@ -74,7 +76,8 @@ export function AppHeader( {wsStatus === "disconnected" && wsErrorMessage && (
- Live updates unavailable. {wsErrorMessage} + Live updates unavailable.{" "} + {wsErrorMessage}
)} diff --git a/src/components/CommentThread.tsx b/src/components/CommentThread.tsx index 8d81fab..9b8c7c3 100644 --- a/src/components/CommentThread.tsx +++ b/src/components/CommentThread.tsx @@ -1,5 +1,7 @@ import React, { useMemo, useRef, useState } from "react"; import { Link } from "react-router"; +import { t } from "@lingui/core/macro" +import { Plural, Trans } from "@lingui/react/macro"; import { API_URL } from "../config/api.ts"; import type { Comment, @@ -103,7 +105,7 @@ function CommentNode({ setReplyError(data.error.message); } } catch { - setReplyError("Could not reach the server. Please try again."); + setReplyError(t`Could not reach the server. Please try again.`); } finally { setSubmitting(false); } @@ -142,7 +144,7 @@ function CommentNode({ setEditError(data.error.message); } } catch { - setEditError("Could not reach the server. Please try again."); + setEditError(t`Could not reach the server. Please try again.`); } finally { setEditSubmitting(false); } @@ -164,7 +166,9 @@ function CommentNode({ />
-

[deleted]

+

+ [deleted] +

{children.length > 0 && ( @@ -222,9 +226,9 @@ function CommentNode({ {comment.updatedAt && ( - + - edited {relativeTime(comment.updatedAt)} + edited {relativeTime(comment.updatedAt)} )} @@ -242,7 +246,7 @@ function CommentNode({ rows={1} /> {editError && ( - + )}
@@ -277,7 +281,7 @@ function CommentNode({ setTimeout(() => replyEditorRef.current?.focus(), 0); }} > - Reply + Reply )} {canEdit && !editOpen && ( @@ -290,7 +294,7 @@ function CommentNode({ setTimeout(() => editEditorRef.current?.focus(), 0); }} > - Edit + Edit )} {canDelete && !editOpen && ( @@ -299,13 +303,13 @@ function CommentNode({ className="comment-action-btn comment-delete-btn" onClick={() => setConfirmDelete(true)} > - Delete + Delete )} {confirmDelete && ( { setConfirmDelete(false); handleDelete(); @@ -322,12 +326,12 @@ function CommentNode({ value={replyBody} onChange={setReplyBody} onSubmit={handleReply} - placeholder="Write a reply…" + placeholder={t`Write a reply…`} autoResize rows={1} /> {replyError && ( - + )}
@@ -418,19 +422,18 @@ export function CommentThread({ setTopLevelError(data.error.message); } } catch { - setTopLevelError("Could not reach the server. Please try again."); + setTopLevelError(t`Could not reach the server. Please try again.`); } finally { setSubmitting(false); } } + const visibleCount = comments.filter((c) => !c.deleted).length; + return (

- {(() => { - const n = comments.filter((c) => !c.deleted).length; - return n === 1 ? "1 comment" : `${n} comments`; - })()} +

{currentUser && ( @@ -450,13 +453,13 @@ export function CommentThread({ value={topLevelBody} onChange={setTopLevelBody} onSubmit={handleTopLevelSubmit} - placeholder="Add a comment…" + placeholder={t`Add a comment…`} autoResize rows={1} /> {topLevelError && ( )} @@ -466,7 +469,7 @@ export function CommentThread({ className="comment-submit-btn" disabled={submitting || !topLevelBody.trim()} > - {submitting ? "Posting…" : "Post comment"} + {submitting ? Posting… : Post comment} {topLevelBody.trim() && ( )} diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx index 4745ede..d4cfea2 100644 --- a/src/components/ConfirmModal.tsx +++ b/src/components/ConfirmModal.tsx @@ -1,5 +1,7 @@ import { useEffect } from "react"; import { createPortal } from "react-dom"; +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro"; interface ConfirmModalProps { message: string; @@ -9,8 +11,10 @@ interface ConfirmModalProps { } export function ConfirmModal( - { message, confirmLabel = "Delete", onConfirm, onCancel }: ConfirmModalProps, + { message, confirmLabel, onConfirm, onCancel }: ConfirmModalProps, ) { + const label = confirmLabel ?? t`Delete`; + useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onCancel(); @@ -24,9 +28,11 @@ export function ConfirmModal(
e.stopPropagation()}>

{message}

- +
diff --git a/src/components/DumpCard.tsx b/src/components/DumpCard.tsx index 57002d5..7336325 100644 --- a/src/components/DumpCard.tsx +++ b/src/components/DumpCard.tsx @@ -1,4 +1,5 @@ import { Link, useNavigate } from "react-router"; +import { Plural, Trans } from "@lingui/react/macro"; import type { Dump } from "../model.ts"; import { relativeTime } from "../utils/relativeTime.ts"; import { dumpUrl } from "../utils/urls.ts"; @@ -78,12 +79,17 @@ export function DumpCard( {dump.commentCount > 0 && ( - {dump.commentCount}{" "} - {dump.commentCount === 1 ? "comment" : "comments"} + )} {dump.isPrivate && isOwner && ( - private + + private + )} diff --git a/src/components/DumpCreateModal.tsx b/src/components/DumpCreateModal.tsx index a249508..84c35ea 100644 --- a/src/components/DumpCreateModal.tsx +++ b/src/components/DumpCreateModal.tsx @@ -1,5 +1,7 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Link } from "react-router"; +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro"; import { API_URL } from "../config/api.ts"; import type { @@ -26,6 +28,13 @@ import { TextEditor } from "./TextEditor.tsx"; import { Modal } from "./Modal.tsx"; import { PlaylistMembershipPanel } from "./PlaylistMembershipPanel.tsx"; import { friendlyFetchError } from "../utils/apiError.ts"; + +function normalizeUrl(input: string): string { + const s = input.trim(); + if (!s || /^https?:\/\//i.test(s)) return s; + if (s.startsWith("//")) return `https:${s}`; + return `https://${s}`; +} import { MAX_FILE_SIZE } from "../config/upload.ts"; type Mode = "url" | "file"; @@ -42,11 +51,16 @@ type UrlPreview = | { status: "done"; richContent: RichContent | null }; function LocalFilePreview({ file }: { file: File }) { - const src = useMemo(() => URL.createObjectURL(file), [file]); + // useRef instead of useMemo+useEffect: StrictMode double-invokes effect + // cleanups, which would revoke the blob URL before the video element can use it. + const blobRef = useRef<{ file: File; url: string } | null>(null); + if (blobRef.current?.file !== file) { + if (blobRef.current) URL.revokeObjectURL(blobRef.current.url); + blobRef.current = { file, url: URL.createObjectURL(file) }; + } + const src = blobRef.current.url; const mime = file.type; - useEffect(() => () => URL.revokeObjectURL(src), [src]); - if (mime.startsWith("image/")) { return {file.name}; } @@ -92,7 +106,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { let trimmed: string; try { - const u = new URL(url.trim()); + const u = new URL(normalizeUrl(url)); if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(); trimmed = u.toString(); } catch { @@ -137,11 +151,11 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { if (tag === "INPUT" || tag === "TEXTAREA") return; const text = e.clipboardData?.getData("text") ?? ""; try { - const u = new URL(text.trim()); + const u = new URL(normalizeUrl(text)); if (u.protocol === "http:" || u.protocol === "https:") { setMode("url"); setFile(null); - setUrl(text.trim()); + setUrl(u.toString()); setSubmitState({ status: "idle" }); } } catch { /* not a URL */ } @@ -158,12 +172,14 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { let res: Response; if (mode === "url") { - if (!url.trim()) { - setSubmitState({ status: "error", error: "URL is required." }); + const normalizedUrl = normalizeUrl(url); + if (!normalizedUrl) { + setSubmitState({ status: "error", error: t`URL is required.` }); return; } + setUrl(normalizedUrl); const body: CreateUrlDumpRequest = { - url: url.trim(), + url: normalizedUrl, comment: comment.trim() || undefined, isPrivate, }; @@ -173,13 +189,16 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { }); } else { if (!file) { - setSubmitState({ status: "error", error: "Please select a file." }); + setSubmitState({ + status: "error", + error: t`Please select a file.`, + }); return; } if (file.size > MAX_FILE_SIZE) { setSubmitState({ status: "error", - error: "File too large (max 50 MB).", + error: t`File too large (max 50 MB).`, }); return; } @@ -254,7 +273,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { return ( @@ -285,14 +304,14 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { }} disabled={submitting} > - šŸ“Ž File + šŸ“Ž File
{submitState.status === "error" && ( )} @@ -301,12 +320,13 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { ? ( <>
- + setUrl(e.target.value)} + onBlur={(e) => setUrl(normalizeUrl(e.target.value))} onPaste={(e) => { const pastedFile = e.clipboardData.files[0]; if (pastedFile) { @@ -325,7 +345,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { />
{urlPreview.status === "loading" && ( -

Fetching preview…

+

Fetching preview…

)} {urlPreview.status === "done" && urlPreview.richContent && ( @@ -348,14 +368,14 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
@@ -367,7 +387,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { disabled={submitting} onClick={() => setIsPrivate(false)} > - Public + Public @@ -386,7 +406,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { className="form-cancel" onClick={onClose} > - Cancel + Cancel @@ -406,9 +428,9 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { <> {createdDump && (

- Dumped!{" "} + Dumped!{" "} - View dump → + View dump →

)} @@ -429,7 +451,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) { className="btn-primary" onClick={onClose} > - Done + Done diff --git a/src/components/FeedTabBar.tsx b/src/components/FeedTabBar.tsx index 9aa0d4a..2a914a3 100644 --- a/src/components/FeedTabBar.tsx +++ b/src/components/FeedTabBar.tsx @@ -1,13 +1,7 @@ import { useLocation, useNavigate } from "react-router"; +import { Trans } from "@lingui/react/macro"; import { useAuth } from "../hooks/useAuth.ts"; - -export type FeedTab = "hot" | "new" | "journal" | "followed"; -export const VALID_TABS = new Set([ - "hot", - "new", - "journal", - "followed", -]); +import { type FeedTab, VALID_TABS } from "../config/feedTabs.ts"; export function FeedTabBar() { const location = useLocation(); @@ -28,21 +22,21 @@ export function FeedTabBar() { className={`feed-sort-btn${tab === "hot" ? " active" : ""}`} onClick={() => setTab("hot")} > - Hot + Hot {user && ( )} diff --git a/src/components/FileDropZone.tsx b/src/components/FileDropZone.tsx index bea7a74..a6aab02 100644 --- a/src/components/FileDropZone.tsx +++ b/src/components/FileDropZone.tsx @@ -1,4 +1,6 @@ import { useCallback, useRef, useState } from "react"; +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro"; import { formatBytes } from "../utils/format.ts"; function fileIcon(mime: string): string { @@ -22,10 +24,12 @@ export function FileDropZone({ file, onChange, disabled, - label = "File", - hint = "Drop a file here", + label, + hint, showLimit = true, }: FileDropZoneProps) { + const resolvedLabel = label ?? t`File`; + const resolvedHint = hint ?? t`Drop a file here`; const inputRef = useRef(null); const [dragging, setDragging] = useState(false); @@ -69,7 +73,7 @@ export function FileDropZone({ return (
- {label && {label}} + {resolvedLabel && {resolvedLabel}}
āœ• @@ -130,11 +134,11 @@ export function FileDropZone({ -

{hint}

+

{resolvedHint}

- or browse files + or browse files

- {showLimit &&

Max 50 MB

} + {showLimit &&

Max 50 MB

}
)}
diff --git a/src/components/FilePreview.tsx b/src/components/FilePreview.tsx index 429c70e..d9b2dbe 100644 --- a/src/components/FilePreview.tsx +++ b/src/components/FilePreview.tsx @@ -1,11 +1,119 @@ +import { useContext, useEffect, useState } from "react"; import { API_URL } from "../config/api.ts"; import type { Dump } from "../model.ts"; import { formatBytes } from "../utils/format.ts"; import { MediaPlayer } from "./MediaPlayer.tsx"; +import { PlayerContext } from "../contexts/PlayerContext.ts"; +import { + BAR_GAP, + BAR_W, + extractPeaks, + NUM_BARS, + VIEWBOX_W, + WAVEFORM_H, +} from "../utils/waveform.ts"; interface FilePreviewProps { dump: Dump; compact?: boolean; + global?: boolean; +} + +// Waveform preview for the dump detail page — routes to global player, +// reflects live play state and position from PlayerContext. +function AudioFilePreview( + { fileUrl, mime, dump }: { fileUrl: string; mime: string; dump: Dump }, +) { + const { current, playing, currentTime, duration, play, togglePlay, seekTo } = + useContext(PlayerContext); + const [peaks, setPeaks] = useState(null); + const isActive = current?.kind === "file" && current.fileUrl === fileUrl; + const progress = isActive && duration > 0 ? currentTime / duration : 0; + + useEffect(() => { + let cancelled = false; + extractPeaks(fileUrl, NUM_BARS) + .then((p) => { if (!cancelled) setPeaks(p); }) + .catch(() => {}); + return () => { cancelled = true; }; + }, [fileUrl]); + + const handlePlayBtn = () => { + if (isActive) togglePlay(); + else play({ kind: "file", fileUrl, mimeType: mime, title: dump.title }); + }; + + const handleWaveformClick = (e: React.MouseEvent) => { + const rect = e.currentTarget.getBoundingClientRect(); + const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); + if (isActive) { + seekTo(ratio * duration); + } else { + // Start playing and seek once it loads — seekTo after play() is a no-op + // until MediaPlayer mounts; the fraction is best-effort on first click + play({ kind: "file", fileUrl, mimeType: mime, title: dump.title }); + } + }; + + const isPlaying = isActive && playing; + + return ( +
+ + {peaks + ? ( + + {Array.from(peaks).map((p, i) => { + const barH = Math.max(p * WAVEFORM_H, 2); + const x = i * (BAR_W + BAR_GAP); + const y = (WAVEFORM_H - barH) / 2; + const played = i / NUM_BARS <= progress; + return ( + + ); + })} + + ) + : ( +
+
+
+ )} +
+ ); } function mimeIcon(mime: string): string { @@ -17,10 +125,13 @@ function mimeIcon(mime: string): string { } export default function FilePreview( - { dump, compact = false }: FilePreviewProps, + { dump, compact = false, global: useGlobal = false }: FilePreviewProps, ) { + const { current, playing, play, togglePlay } = useContext(PlayerContext); const fileUrl = `${API_URL}/api/files/${dump.id}?v=${dump.fileSize ?? 0}`; const mime = dump.fileMime ?? ""; + const isMedia = mime.startsWith("video/") || mime.startsWith("audio/"); + const isPlaying = current?.kind === "file" && current.fileUrl === fileUrl; if (compact) { if (mime.startsWith("image/")) { @@ -35,6 +146,45 @@ export default function FilePreview( /> ); } + if (mime.startsWith("video/")) { + return ( + + ); + } + if (mime.startsWith("audio/")) { + return ( + + ); + } return {mimeIcon(mime)}; } @@ -45,10 +195,37 @@ export default function FilePreview( } if (mime.startsWith("video/")) { + if (useGlobal) { + const videoActive = isPlaying; + const videoPlaying = videoActive && playing; + return ( + + ); + } return ; } if (mime.startsWith("audio/")) { + if (useGlobal) { + return ; + } return ; } diff --git a/src/components/FollowButton.tsx b/src/components/FollowButton.tsx index d1a2f0d..fc431b8 100644 --- a/src/components/FollowButton.tsx +++ b/src/components/FollowButton.tsx @@ -1,3 +1,5 @@ +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro"; import { useAuth } from "../hooks/useAuth.ts"; import { useFollows } from "../hooks/useFollows.ts"; @@ -29,10 +31,10 @@ export function FollowUserButton( onClick={() => isFollowing ? unfollowUser(targetUserId) : followUser(targetUserId)} aria-label={isFollowing - ? `Unfollow ${targetUsername}` - : `Follow ${targetUsername}`} + ? t`Unfollow ${targetUsername}` + : t`Follow ${targetUsername}`} > - {isFollowing ? "Following" : "Follow"} + {isFollowing ? Following : Follow} ); } @@ -57,9 +59,9 @@ export function FollowPlaylistButton( isFollowing ? unfollowPlaylist(targetPlaylistId) : followPlaylist(targetPlaylistId)} - aria-label={isFollowing ? "Unfollow playlist" : "Follow playlist"} + aria-label={isFollowing ? t`Unfollow playlist` : t`Follow playlist`} > - {isFollowing ? "Following" : "Follow"} + {isFollowing ? Following : Follow} ); } diff --git a/src/components/GlobalPlayer.tsx b/src/components/GlobalPlayer.tsx index e660cd5..9ad6048 100644 --- a/src/components/GlobalPlayer.tsx +++ b/src/components/GlobalPlayer.tsx @@ -1,10 +1,23 @@ import { useContext, useEffect, useRef, useState } from "react"; import { PlayerContext } from "../contexts/PlayerContext.ts"; +import { MediaPlayer } from "./MediaPlayer.tsx"; + +function itemKey(item: { kind: string; embedUrl?: string; fileUrl?: string } | null) { + if (!item) return null; + return item.kind === "embed" ? item.embedUrl : item.fileUrl; +} export function GlobalPlayer() { - const { current, stop } = useContext(PlayerContext); + const { current, stop, seekRef, toggleRef, onPlayStateChange, onTimeUpdate } = + useContext(PlayerContext); const ref = useRef(null); const [reduced, setReduced] = useState(false); + const [prevKey, setPrevKey] = useState(itemKey(current)); + + if (prevKey !== itemKey(current)) { + setPrevKey(itemKey(current)); + if (current) setReduced(false); + } useEffect(() => { if (!current) { @@ -33,23 +46,21 @@ export function GlobalPlayer() { }; }, [current]); - useEffect(() => { - if (current) setReduced(false); - }, [current?.embedUrl]); - if (!current) return null; + const typeClass = current.kind === "embed" + ? current.type + : current.mimeType.startsWith("video/") ? "file-video" : "file-audio"; + + const title = current.title ?? (current.kind === "embed" ? current.embedUrl : current.fileUrl); + return (
- - {current.title ?? current.embedUrl} - + {title}
-
-