Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad075d03e6 | |||
| fc750703ac | |||
| f06f2cbc25 | |||
| 0a4f5e132c | |||
| 6dce96881c | |||
| 510dc51d7a | |||
| decac43c34 | |||
| efcc15fc55 | |||
| 53d9db174c | |||
| a689322f4a | |||
| aaf45129aa | |||
| 48e1502bd1 | |||
| b41624edea |
@@ -28,6 +28,7 @@
|
||||
"prom-client": "^15.1.3",
|
||||
"react": "^19.2.6",
|
||||
"satori": "^0.26.0",
|
||||
"sharp": "^0.35.3",
|
||||
"twemoji": "^14.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+372
-38
@@ -7,52 +7,52 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
'@pnpm/exe':
|
||||
specifier: 11.5.2
|
||||
version: 11.5.2
|
||||
specifier: 11.10.0
|
||||
version: 11.10.0
|
||||
pnpm:
|
||||
specifier: 11.5.2
|
||||
version: 11.5.2
|
||||
specifier: 11.10.0
|
||||
version: 11.10.0
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe@11.5.2':
|
||||
resolution: {integrity: sha512-4UFnP2rhNu1xjAQ+I1GdIUUEtCJuTYJlbpiWSFA4POAID3Lpt+2vrjImWO7eOJ7iCY3vpc4TFe2IW3sAolW4Kg==}
|
||||
'@pnpm/exe@11.10.0':
|
||||
resolution: {integrity: sha512-mrmfi2C7LpZkyq0voKKye6MzrK8/K7tYQRiSB/jqOiwnFtQxxcA3xGTY9cEsrGpNl2ClPecQofLuj+BO8AIsxw==}
|
||||
hasBin: true
|
||||
|
||||
'@pnpm/linux-arm64@11.5.2':
|
||||
resolution: {integrity: sha512-MbJySnu2y9cCBqlODLjUlZ87JnRC3Inq40rvGHWJSrSQ0PnuHeSw2NDMnLI8Hf9hCY+ooussRc5iiR4IAkjUvg==}
|
||||
'@pnpm/linux-arm64@11.10.0':
|
||||
resolution: {integrity: sha512-NbvDeUfs0SJuli9OPvgVvmnlbo2DvJ861XGXKrzgLu5AuTnLDLXgbZEEUd8mJ5I0YNrqOVXSWVfWEqiAazWzPA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@pnpm/linux-x64@11.5.2':
|
||||
resolution: {integrity: sha512-g6g2BGpQA47wUACy6B1MdeSHPtnl6x4AeCg0IOWQ7xXorEtC+VRiSHhLpA5kByFGeSwyYh/nLc7mLul5DAaELw==}
|
||||
'@pnpm/linux-x64@11.10.0':
|
||||
resolution: {integrity: sha512-kdgb8BXZ/3XQ0x2cOmgLmsij7+SUIqd1bcV6OZhdGzQiDrOY6FAPrR+Y2Bp+NjrrhjzMHVm5pZrrmdeC67ymSQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@pnpm/linuxstatic-arm64@11.5.2':
|
||||
resolution: {integrity: sha512-xTxs9BLxYW39BPNGnmvYCUBnMPWm4mzmzujmdYbpRxDnBXrx55qPR5K/3LSohX7VrmsdDrYxuH6AmG1AaOlIfA==}
|
||||
'@pnpm/linuxstatic-arm64@11.10.0':
|
||||
resolution: {integrity: sha512-JE1WrSyKGvqGQgWzqrMXn//ehedpiRax3hi1oP+v6mhvcnlJD1lpfXsjSfIFl9weTWC5KVtFbxSNKbKWfP+v6g==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/linuxstatic-x64@11.5.2':
|
||||
resolution: {integrity: sha512-RGmmc/SoGLD90gmOHcU85UEKNoNRstLvizli4wzDASmETz/VeqJOqU5nD1YBgjzcP72sUMS352dh4bmzTfKyvQ==}
|
||||
'@pnpm/linuxstatic-x64@11.10.0':
|
||||
resolution: {integrity: sha512-1TBZVRkWb78GnsusIfVgwz20MOOW0fyehW+qLL3MxE9vT0IedNWsAhe3KWXgEvtC4M7NtMt55uQQJm+1egwBkA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/macos-arm64@11.5.2':
|
||||
resolution: {integrity: sha512-gW3A2jRlC3SJRw8qX2SAzjMIu9o98daTSqCKzeeYcjF/uEbtbz3dn4HqYrYffBnenKbc4hsgZQmNOHAvUKIlSg==}
|
||||
'@pnpm/macos-arm64@11.10.0':
|
||||
resolution: {integrity: sha512-94AVpPixBqyNT6SHYvIKFb1bfaHR5vxBzZsiFJahNSlGkgyPrgzmcqDiloZ/Jl+zxJd8L5PU1ddP0Q9PZnRqlA==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/win-arm64@11.5.2':
|
||||
resolution: {integrity: sha512-+VJCDoH/pRzLXBikwjvxgAnGfQufT8EALBX8cfSmrwD40JABUZvgPtjBjde7OwEoK/XwtlH8w+ZceFV0K3/YHQ==}
|
||||
'@pnpm/win-arm64@11.10.0':
|
||||
resolution: {integrity: sha512-g2Ymnq+LgVyZaWsGBQSlpIcBCOOxyLky2UX+kTwGiIXnj6k/xXqZR0ZJLuxeiGNh/CVmhftOqokpyJNzyj8kng==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/win-x64@11.5.2':
|
||||
resolution: {integrity: sha512-zgglREh75RbFgV/E0tNRS03ElX+hJOV43KRSSeaboxtj3ei1rrguxOgOCXUs/GsizoHVsuD+qXGABE4Kc4GMCg==}
|
||||
'@pnpm/win-x64@11.10.0':
|
||||
resolution: {integrity: sha512-kCHYZudUEBjrEchgnJUnNHCdvLXpUZun2z0rMAeP5DymsaXwEVvVzGj220iR/NyhgDocJUmgtTKtwL/ejL785Q==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@@ -116,45 +116,45 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pnpm@11.5.2:
|
||||
resolution: {integrity: sha512-ccYx44IGbvwlYl1c8CkHXeB7YbN/bic1D72Esb2lhkyMGWetwoB3a0XDCnFcA1mjvgj+9C1bsJ4rmQKZeWkpFg==}
|
||||
pnpm@11.10.0:
|
||||
resolution: {integrity: sha512-C3+LmAYAMZBMAX46QesYehbUDuuCm5XE+MsDaBdh/Eq1PdIZEVubRH9NzhoFohR2RGHn03AzkqnzL5URzoyGyA==}
|
||||
engines: {node: '>=22.13'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe@11.5.2':
|
||||
'@pnpm/exe@11.10.0':
|
||||
dependencies:
|
||||
'@reflink/reflink': 0.1.19
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
'@pnpm/linux-arm64': 11.5.2
|
||||
'@pnpm/linux-x64': 11.5.2
|
||||
'@pnpm/linuxstatic-arm64': 11.5.2
|
||||
'@pnpm/linuxstatic-x64': 11.5.2
|
||||
'@pnpm/macos-arm64': 11.5.2
|
||||
'@pnpm/win-arm64': 11.5.2
|
||||
'@pnpm/win-x64': 11.5.2
|
||||
'@pnpm/linux-arm64': 11.10.0
|
||||
'@pnpm/linux-x64': 11.10.0
|
||||
'@pnpm/linuxstatic-arm64': 11.10.0
|
||||
'@pnpm/linuxstatic-x64': 11.10.0
|
||||
'@pnpm/macos-arm64': 11.10.0
|
||||
'@pnpm/win-arm64': 11.10.0
|
||||
'@pnpm/win-x64': 11.10.0
|
||||
|
||||
'@pnpm/linux-arm64@11.5.2':
|
||||
'@pnpm/linux-arm64@11.10.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linux-x64@11.5.2':
|
||||
'@pnpm/linux-x64@11.10.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linuxstatic-arm64@11.5.2':
|
||||
'@pnpm/linuxstatic-arm64@11.10.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linuxstatic-x64@11.5.2':
|
||||
'@pnpm/linuxstatic-x64@11.10.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/macos-arm64@11.5.2':
|
||||
'@pnpm/macos-arm64@11.10.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/win-arm64@11.5.2':
|
||||
'@pnpm/win-arm64@11.10.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/win-x64@11.5.2':
|
||||
'@pnpm/win-x64@11.10.0':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-darwin-arm64@0.1.19':
|
||||
@@ -194,7 +194,7 @@ snapshots:
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
pnpm@11.5.2: {}
|
||||
pnpm@11.10.0: {}
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
@@ -237,6 +237,9 @@ importers:
|
||||
satori:
|
||||
specifier: ^0.26.0
|
||||
version: 0.26.0
|
||||
sharp:
|
||||
specifier: ^0.35.3
|
||||
version: 0.35.3(@types/node@24.12.2)
|
||||
twemoji:
|
||||
specifier: ^14.0.2
|
||||
version: 14.0.2
|
||||
@@ -295,6 +298,9 @@ packages:
|
||||
resolution: {integrity: sha512-NJy02bIKrWlE2NQkRV1kT0Cj0ixbuxlF/MejBdo4cPWAa9v3oZexvAcjjb0zaOYeABkaU14iyIhvn2G4e/oLpw==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@emnapi/runtime@1.11.2':
|
||||
resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -451,6 +457,168 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@img/colour@1.1.0':
|
||||
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.35.3':
|
||||
resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-darwin-x64@0.35.3':
|
||||
resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-freebsd-wasm32@0.35.3':
|
||||
resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
os: [freebsd]
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.3.2':
|
||||
resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.3.2':
|
||||
resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.3.2':
|
||||
resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.3.2':
|
||||
resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.3.2':
|
||||
resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.3.2':
|
||||
resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.3.2':
|
||||
resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.3.2':
|
||||
resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
|
||||
resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
|
||||
resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.35.3':
|
||||
resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.35.3':
|
||||
resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.35.3':
|
||||
resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-riscv64@0.35.3':
|
||||
resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.35.3':
|
||||
resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.35.3':
|
||||
resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.35.3':
|
||||
resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.35.3':
|
||||
resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.35.3':
|
||||
resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
|
||||
'@img/sharp-webcontainers-wasm32@0.35.3':
|
||||
resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [wasm32]
|
||||
|
||||
'@img/sharp-win32-arm64@0.35.3':
|
||||
resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-ia32@0.35.3':
|
||||
resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==}
|
||||
engines: {node: ^20.9.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-x64@0.35.3':
|
||||
resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@opentelemetry/api@1.9.0':
|
||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -692,6 +860,10 @@ packages:
|
||||
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1020,6 +1192,11 @@ packages:
|
||||
semver-compare@1.0.0:
|
||||
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
|
||||
|
||||
semver@7.8.5:
|
||||
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
send@0.19.0:
|
||||
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -1031,6 +1208,15 @@ packages:
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
sharp@0.35.3:
|
||||
resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1215,6 +1401,11 @@ snapshots:
|
||||
'@atproto/lexicon': 0.7.1
|
||||
zod: 3.25.76
|
||||
|
||||
'@emnapi/runtime@1.11.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
@@ -1293,6 +1484,112 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@img/colour@1.1.0': {}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-x64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-x64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-freebsd-wasm32@0.35.3':
|
||||
dependencies:
|
||||
'@img/sharp-wasm32': 0.35.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-ppc64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-ppc64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-riscv64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-riscv64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-s390x@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-s390x': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-x64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-x64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.35.3':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-wasm32@0.35.3':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.11.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-webcontainers-wasm32@0.35.3':
|
||||
dependencies:
|
||||
'@img/sharp-wasm32': 0.35.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.35.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-ia32@0.35.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-x64@0.35.3':
|
||||
optional: true
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@resvg/resvg-js-android-arm-eabi@2.6.2':
|
||||
@@ -1500,6 +1797,8 @@ snapshots:
|
||||
|
||||
destroy@1.2.0: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
@@ -1881,6 +2180,8 @@ snapshots:
|
||||
|
||||
semver-compare@1.0.0: {}
|
||||
|
||||
semver@7.8.5: {}
|
||||
|
||||
send@0.19.0:
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
@@ -1910,6 +2211,39 @@ snapshots:
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
sharp@0.35.3(@types/node@24.12.2):
|
||||
dependencies:
|
||||
'@img/colour': 1.1.0
|
||||
detect-libc: 2.1.2
|
||||
semver: 7.8.5
|
||||
optionalDependencies:
|
||||
'@img/sharp-darwin-arm64': 0.35.3
|
||||
'@img/sharp-darwin-x64': 0.35.3
|
||||
'@img/sharp-freebsd-wasm32': 0.35.3
|
||||
'@img/sharp-libvips-darwin-arm64': 1.3.2
|
||||
'@img/sharp-libvips-darwin-x64': 1.3.2
|
||||
'@img/sharp-libvips-linux-arm': 1.3.2
|
||||
'@img/sharp-libvips-linux-arm64': 1.3.2
|
||||
'@img/sharp-libvips-linux-ppc64': 1.3.2
|
||||
'@img/sharp-libvips-linux-riscv64': 1.3.2
|
||||
'@img/sharp-libvips-linux-s390x': 1.3.2
|
||||
'@img/sharp-libvips-linux-x64': 1.3.2
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
|
||||
'@img/sharp-linux-arm': 0.35.3
|
||||
'@img/sharp-linux-arm64': 0.35.3
|
||||
'@img/sharp-linux-ppc64': 0.35.3
|
||||
'@img/sharp-linux-riscv64': 0.35.3
|
||||
'@img/sharp-linux-s390x': 0.35.3
|
||||
'@img/sharp-linux-x64': 0.35.3
|
||||
'@img/sharp-linuxmusl-arm64': 0.35.3
|
||||
'@img/sharp-linuxmusl-x64': 0.35.3
|
||||
'@img/sharp-webcontainers-wasm32': 0.35.3
|
||||
'@img/sharp-win32-arm64': 0.35.3
|
||||
'@img/sharp-win32-ia32': 0.35.3
|
||||
'@img/sharp-win32-x64': 0.35.3
|
||||
'@types/node': 24.12.2
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type RequestHandler,
|
||||
type Response,
|
||||
} from 'express'
|
||||
import sharp from 'sharp'
|
||||
|
||||
import {type AppContext} from '../context.js'
|
||||
import {httpLogger} from '../logger.js'
|
||||
@@ -40,11 +41,38 @@ export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
|
||||
return res.status(500).end('server error')
|
||||
}
|
||||
|
||||
// Cards render avatars at ~210px or smaller, but the CDN's full-size
|
||||
// variant is up to 1000x1000. satori parses and resvg rasterizes every
|
||||
// image at its intrinsic size: ~4MB of native RGBA per avatar per render,
|
||||
// x7 avatars per starter-pack card. That native churn (plus satori's
|
||||
// 500-entry image LRU holding the oversized data URIs) is what OOMs the
|
||||
// service. Downscaling to the largest displayed size before handing the
|
||||
// bytes to the render pipeline cuts decoded pixel volume ~15x with zero
|
||||
// visible quality change.
|
||||
const MAX_AVATAR_RENDER_PX = 256
|
||||
|
||||
// sharp's libvips operation cache holds decoded images in native memory
|
||||
// (50MB default per process). This service re-renders unique avatars, so
|
||||
// cache hits are rare and the cache is pure RSS overhead: disable it.
|
||||
sharp.cache(false)
|
||||
|
||||
export async function getImage(url: string) {
|
||||
const response = await fetch(ensureJpeg(url))
|
||||
const arrayBuf = await response.arrayBuffer()
|
||||
if (response.status !== 200) return null
|
||||
return Buffer.from(arrayBuf)
|
||||
return (
|
||||
sharp(arrayBuf)
|
||||
.resize({
|
||||
width: MAX_AVATAR_RENDER_PX,
|
||||
height: MAX_AVATAR_RENDER_PX,
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
// quality 95: this is a second lossy encode over an already-lossy CDN
|
||||
// jpeg, and the default (80) visibly softens avatars at card sizes
|
||||
.jpeg({quality: 95})
|
||||
.toBuffer()
|
||||
)
|
||||
}
|
||||
|
||||
// CDN URLs end with @jpeg, @webp, or no extension (which may default to webp).
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
|
||||
+2
-2
@@ -247,8 +247,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
|
||||
@@ -671,9 +671,6 @@
|
||||
}
|
||||
},
|
||||
"src/components/forms/TextField.tsx": {
|
||||
"@typescript-eslint/no-unsafe-member-access": {
|
||||
"count": 3
|
||||
},
|
||||
"react-hooks/refs": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -987,11 +984,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/lib/merge-refs.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/lib/notifications/notifications.ts": {
|
||||
"@typescript-eslint/no-floating-promises": {
|
||||
"count": 5
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.127.0",
|
||||
"version": "1.128.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=24.18.0"
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
diff --git a/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts b/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts
|
||||
index 48178d9246ab94d701983ac20473983eb746d1a6..451d38e6e20646c32795db3be28fbea57de1d12f 100644
|
||||
--- a/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts
|
||||
+++ b/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts
|
||||
@@ -172,6 +172,18 @@ function useChatKeyboard(
|
||||
|
||||
currentHeight.value = e.height;
|
||||
|
||||
+ if (scrollViewRef() == null) {
|
||||
+ // The scroll view can be detached or not yet attached while a
|
||||
+ // keyboard animation is running (e.g. the chat list re-creating its
|
||||
+ // scroll component mid-animation). Every branch below eventually
|
||||
+ // calls scrollTo (directly or via clampScrollIfNeeded); on Paper
|
||||
+ // reanimated's scrollTo would hand the null ref to the native
|
||||
+ // _scrollToPaper HostFunction, which throws "Value is null, expected
|
||||
+ // a number" and crashes in release builds. currentHeight is kept up
|
||||
+ // to date above so the animated style keeps committing.
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
if (inverted) {
|
||||
// Skip post-interactive snap-back (duration === -1)
|
||||
if (e.duration === -1) {
|
||||
diff --git a/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts b/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts
|
||||
index 0f6d7c67a307885310ab184fdf9e7a5c7b296825..1a01093e7909973cef5268f999158df389e77634 100644
|
||||
--- a/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts
|
||||
|
||||
Generated
+3
-3
@@ -228,7 +228,7 @@ patchedDependencies:
|
||||
react-native-compressor@1.13.0: 58379dfaace6ced8590cb341c77f2ca8099dfa8f7df6297032ec51de767a9925
|
||||
react-native-date-picker@5.0.13: f2a6697da7a7ca79b4f39efda142eee1b82db6de3aa5010ad4bd59df41fe2ce1
|
||||
react-native-drawer-layout@4.2.3: 74f2c043cc22ab87054f219e7c7373a509b779b18bfa79d27e7d051d73355130
|
||||
react-native-keyboard-controller@1.21.8: 2f6791837622fb391c3e41fc044b91ddedbe503da14da86165ee0a73924b05f1
|
||||
react-native-keyboard-controller@1.21.8: ac52bf7502ff3ad34a8594b5e1a916b96bf87a2523b6abc87c31f03607d52271
|
||||
react-native-pager-view@6.8.0: 0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108
|
||||
react-native-reanimated@3.19.1: 97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6
|
||||
react-native-svg@15.12.1: 31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9
|
||||
@@ -639,7 +639,7 @@ importers:
|
||||
version: 2.28.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-keyboard-controller:
|
||||
specifier: ^1.21.8
|
||||
version: 1.21.8(patch_hash=2f6791837622fb391c3e41fc044b91ddedbe503da14da86165ee0a73924b05f1)(react-native-reanimated@3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
version: 1.21.8(patch_hash=ac52bf7502ff3ad34a8594b5e1a916b96bf87a2523b6abc87c31f03607d52271)(react-native-reanimated@3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-pager-view:
|
||||
specifier: 6.8.0
|
||||
version: 6.8.0(patch_hash=0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
@@ -17878,7 +17878,7 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
react-native-keyboard-controller@1.21.8(patch_hash=2f6791837622fb391c3e41fc044b91ddedbe503da14da86165ee0a73924b05f1)(react-native-reanimated@3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
react-native-keyboard-controller@1.21.8(patch_hash=ac52bf7502ff3ad34a8594b5e1a916b96bf87a2523b6abc87c31f03607d52271)(react-native-reanimated@3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {createStaticClick, SimpleInlineLinkText} from '#/components/Link'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {IS_IOS} from '#/env'
|
||||
|
||||
/**
|
||||
* Explains what sharing on-device age signals reveals, shown alongside the
|
||||
* "Share age range" button on the age assurance surfaces (the no-access screen
|
||||
* and the account settings card).
|
||||
*
|
||||
* Platform-split so the copy can name the OS account the age range comes from
|
||||
* (Apple Account vs Google Account) and reassure users that only a range is
|
||||
* shared. Device signals are only supported on iOS and Android, so these are
|
||||
* the only two cases.
|
||||
*/
|
||||
export function DeviceSignalsNotice({onPressKws}: {onPressKws: () => void}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
|
||||
return (
|
||||
<Text style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}>
|
||||
{IS_IOS ? (
|
||||
<Trans comment="Shown next to the 'Share age range' button when on-device age verification is available. KWS is the name of a third-party verification partner.">
|
||||
Sharing your age range reveals only the range associated with your
|
||||
Apple Account – for example, that you’re at least 18.{' '}
|
||||
<Text style={[a.text_sm, a.font_bold]}>
|
||||
Your exact age and birthday are never shared,
|
||||
</Text>{' '}
|
||||
and this data never leaves this device. Therefore, it only enables
|
||||
access on this device. Alternatively,{' '}
|
||||
<SimpleInlineLinkText
|
||||
label={l`Verify now using KWS`}
|
||||
{...createStaticClick(() => {
|
||||
onPressKws()
|
||||
})}>
|
||||
you can use our trusted partner, KWS
|
||||
</SimpleInlineLinkText>
|
||||
, to complete your verification and enable access on all platforms.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans comment="Shown next to the 'Share age range' button when on-device age verification is available. KWS is the name of a third-party verification partner.">
|
||||
Sharing your age range reveals only the range associated with your
|
||||
Google Account – for example, that you’re at least 18.{' '}
|
||||
<Text style={[a.text_sm, a.font_bold]}>
|
||||
Your exact age and birthday are never shared,
|
||||
</Text>{' '}
|
||||
and this data never leaves this device. Therefore, it only enables
|
||||
access on this device. Alternatively,{' '}
|
||||
<SimpleInlineLinkText
|
||||
label={l`Verify now using KWS`}
|
||||
{...createStaticClick(() => {
|
||||
onPressKws()
|
||||
})}>
|
||||
you can use our trusted partner, KWS
|
||||
</SimpleInlineLinkText>
|
||||
, to complete your verification and enable access on all platforms.
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {BottomSheetOutlet} from '#/../modules/bottom-sheet'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {DeviceSignalsNotice} from '#/ageAssurance/components/DeviceSignalsNotice'
|
||||
import {useAgeAssuranceServerDataContext} from '#/ageAssurance/data'
|
||||
import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
|
||||
import {useAgeAssuranceVerificationFlow} from '#/ageAssurance/useVerificationFlow'
|
||||
@@ -428,23 +429,7 @@ function AccessSection() {
|
||||
</Button>
|
||||
|
||||
{useDeviceSignals ? (
|
||||
<Text
|
||||
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}>
|
||||
<Trans>
|
||||
Sharing your age data uses information stored on your
|
||||
device, and will therefore only work on this device.
|
||||
Alternatively,{' '}
|
||||
<SimpleInlineLinkText
|
||||
label={l`Verify now using KWS`}
|
||||
{...createStaticClick(() => {
|
||||
openInitDialog()
|
||||
})}>
|
||||
you can use our trusted partner, KWS
|
||||
</SimpleInlineLinkText>
|
||||
, to complete your verification and enable access on all
|
||||
platforms.
|
||||
</Trans>
|
||||
</Text>
|
||||
<DeviceSignalsNotice onPressKws={openInitDialog} />
|
||||
) : lastInitiatedAt && timeAgo && diff ? (
|
||||
<Text
|
||||
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function useAgeAssuranceVerificationFlow({
|
||||
|
||||
const verifyCta =
|
||||
allowsDeviceVerification && !deviceSignalsFailed
|
||||
? l`Share age data`
|
||||
? l`Share age range`
|
||||
: hasInitiated
|
||||
? l`Verify again`
|
||||
: l`Verify now`
|
||||
|
||||
@@ -1404,6 +1404,8 @@ export type Events = {
|
||||
uploadId: string
|
||||
engine: string
|
||||
errorClass: string
|
||||
/** Truncated to 256 chars */
|
||||
errorMessage: string
|
||||
elapsedMs: number
|
||||
}
|
||||
'video:upload:uploadStarted': {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {DeviceSignalsNotice} from '#/ageAssurance/components/DeviceSignalsNotice'
|
||||
import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
|
||||
import {useAgeAssuranceVerificationFlow} from '#/ageAssurance/useVerificationFlow'
|
||||
import {createGeolocationString} from '#/ageAssurance/util'
|
||||
@@ -164,23 +165,7 @@ function Inner({style}: ViewStyleProp & {}) {
|
||||
</Button>
|
||||
|
||||
{useDeviceSignals ? (
|
||||
<Text
|
||||
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}>
|
||||
<Trans>
|
||||
Sharing your age data uses information stored on your
|
||||
device, and will therefore only work on this device.
|
||||
Alternatively,{' '}
|
||||
<InlineLinkText
|
||||
label={l`Verify now using KWS`}
|
||||
{...createStaticClick(() => {
|
||||
openInitDialog()
|
||||
})}>
|
||||
you can use our trusted partner, KWS
|
||||
</InlineLinkText>
|
||||
, to complete your verification and enable access on all
|
||||
platforms.
|
||||
</Trans>
|
||||
</Text>
|
||||
<DeviceSignalsNotice onPressKws={openInitDialog} />
|
||||
) : lastInitiatedAt && timeAgo && diff ? (
|
||||
<Text
|
||||
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}
|
||||
|
||||
@@ -207,7 +207,7 @@ export function createInput(Component: typeof TextInput) {
|
||||
|
||||
const refs = mergeRefs([ctx.inputRef, inputRef!].filter(Boolean))
|
||||
|
||||
const flattened = StyleSheet.flatten([
|
||||
const flattened = StyleSheet.flatten<TextStyle>([
|
||||
a.relative,
|
||||
a.z_20,
|
||||
a.flex_1,
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||
import {
|
||||
ALL_FORMATS,
|
||||
type AudioCodec,
|
||||
BlobSource,
|
||||
BufferTarget,
|
||||
canEncodeAudio,
|
||||
canEncodeVideo,
|
||||
Conversion,
|
||||
Input,
|
||||
Mp4OutputFormat,
|
||||
Output,
|
||||
type Input as MediaBunnyInput,
|
||||
type VideoCodec,
|
||||
WebMOutputFormat,
|
||||
} from 'mediabunny'
|
||||
|
||||
import {VIDEO_MAX_SIZE} from '#/lib/constants'
|
||||
@@ -23,6 +14,7 @@ import {
|
||||
COMPRESSION_MIN_SIZE_BYTES,
|
||||
COMPRESSION_TARGET_BITRATE,
|
||||
} from './constants'
|
||||
import {loadMediaBunny} from './mediabunny'
|
||||
import {type CompressedVideo, type ProbedMetadata} from './types'
|
||||
|
||||
// Codecs to try in order of preference
|
||||
@@ -44,8 +36,12 @@ export async function compressVideo(
|
||||
hasWebCodecs: hasWebCodecs(),
|
||||
})
|
||||
|
||||
const response = await fetch(asset.uri)
|
||||
const blob = await response.blob()
|
||||
/*
|
||||
* Prefer the original File over re-fetching the blob URL. A fetch round
|
||||
* trip copies the bytes and fails with a bare TypeError if the URL was
|
||||
* revoked or the read fails - the top web compressFailed error class.
|
||||
*/
|
||||
const blob = asset.file ?? (await (await fetch(asset.uri)).blob())
|
||||
|
||||
const isGif = blob.type === 'image/gif'
|
||||
const hasCodecs = hasWebCodecs()
|
||||
@@ -112,6 +108,7 @@ export async function compressVideo(
|
||||
}
|
||||
|
||||
async function probeWithMediaBunny(blob: Blob): Promise<ProbedMetadata> {
|
||||
const {ALL_FORMATS, BlobSource, Input} = await loadMediaBunny()
|
||||
const input = new Input({source: new BlobSource(blob), formats: ALL_FORMATS})
|
||||
try {
|
||||
const videoTrack = await input.getPrimaryVideoTrack()
|
||||
@@ -153,6 +150,7 @@ async function findEncodableVideoCodec(
|
||||
width: number,
|
||||
height: number,
|
||||
): Promise<{codec: VideoCodec; useWebM: boolean} | null> {
|
||||
const {canEncodeVideo} = await loadMediaBunny()
|
||||
for (const codec of VIDEO_CODECS) {
|
||||
const canEncode = await canEncodeVideo(codec, {
|
||||
width,
|
||||
@@ -179,9 +177,10 @@ const AUDIO_CODECS_MP4: AudioCodec[] = ['aac']
|
||||
const AUDIO_CODECS_WEBM: AudioCodec[] = ['opus', 'vorbis']
|
||||
|
||||
async function findEncodableAudioCodec(
|
||||
audioTrack: Awaited<ReturnType<Input['getPrimaryAudioTrack']>>,
|
||||
audioTrack: Awaited<ReturnType<MediaBunnyInput['getPrimaryAudioTrack']>>,
|
||||
useWebM: boolean,
|
||||
): Promise<{codec: AudioCodec} | null> {
|
||||
const {canEncodeAudio} = await loadMediaBunny()
|
||||
if (!audioTrack) {
|
||||
return null
|
||||
}
|
||||
@@ -228,6 +227,17 @@ async function doCompression(
|
||||
): Promise<CompressedVideo> {
|
||||
const {onProgress, signal} = opts
|
||||
|
||||
const {
|
||||
ALL_FORMATS,
|
||||
BlobSource,
|
||||
BufferTarget,
|
||||
Conversion,
|
||||
Input,
|
||||
Mp4OutputFormat,
|
||||
Output,
|
||||
WebMOutputFormat,
|
||||
} = await loadMediaBunny()
|
||||
|
||||
const input = new Input({
|
||||
source: new BlobSource(blob),
|
||||
formats: ALL_FORMATS,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
type MediaBunnyModule = typeof import('mediabunny')
|
||||
|
||||
let promise: Promise<MediaBunnyModule> | undefined
|
||||
|
||||
/**
|
||||
* Lazily loads mediabunny into its own webpack chunk and caches the resulting
|
||||
* promise so the module is fetched at most once. If the import fails (e.g. a
|
||||
* transient chunk-load error), the cached promise is reset so a later call can
|
||||
* retry the load.
|
||||
*/
|
||||
export function loadMediaBunny(): Promise<MediaBunnyModule> {
|
||||
promise ??= import(/* webpackChunkName: "mediabunny" */ 'mediabunny').catch(
|
||||
e => {
|
||||
// reset so a later attempt can retry after a transient chunk-load failure
|
||||
promise = undefined
|
||||
throw e
|
||||
},
|
||||
)
|
||||
return promise
|
||||
}
|
||||
@@ -26,6 +26,11 @@ function errorClass(e: unknown): string {
|
||||
return 'Unknown'
|
||||
}
|
||||
|
||||
function errorMessage(e: unknown): string {
|
||||
const message = e instanceof Error ? e.message : String(e)
|
||||
return message.slice(0, 256)
|
||||
}
|
||||
|
||||
export type VideoTelemetry = {
|
||||
readonly uploadId: string
|
||||
readonly engine: string
|
||||
@@ -208,6 +213,7 @@ export function createVideoTelemetry({
|
||||
uploadId,
|
||||
engine,
|
||||
errorClass: errorClass(e),
|
||||
errorMessage: errorMessage(e),
|
||||
elapsedMs: Date.now() - phaseStartedAt,
|
||||
})
|
||||
endTxn('error')
|
||||
|
||||
+32
-11
@@ -1,3 +1,22 @@
|
||||
import {type Ref, type RefCallback} from 'react'
|
||||
|
||||
/**
|
||||
* Assigns a value to a ref.
|
||||
* @param ref The ref to assign the value to.
|
||||
* @param value The value to assign to the ref.
|
||||
* @returns The ref cleanup callback, if any.
|
||||
*/
|
||||
function assignRef<T>(
|
||||
ref: Ref<T> | undefined | null,
|
||||
value: T | null,
|
||||
): ReturnType<RefCallback<T>> {
|
||||
if (typeof ref === 'function') {
|
||||
return ref(value)
|
||||
} else if (ref) {
|
||||
ref.current = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This TypeScript function merges multiple React refs into a single ref callback.
|
||||
* When developing low level UI components, it is common to have to use a local ref
|
||||
@@ -12,16 +31,18 @@
|
||||
* @returns The function `mergeRefs` is being returned. It takes an array of mutable or legacy refs and
|
||||
* returns a ref callback function that can be used to merge multiple refs into a single ref.
|
||||
*/
|
||||
export function mergeRefs<T = any>(
|
||||
refs: Array<React.MutableRefObject<T> | React.Ref<T> | undefined>,
|
||||
): React.RefCallback<T> {
|
||||
return value => {
|
||||
refs.forEach(ref => {
|
||||
if (typeof ref === 'function') {
|
||||
ref(value)
|
||||
} else if (ref != null) {
|
||||
;(ref as React.MutableRefObject<T | null>).current = value
|
||||
}
|
||||
})
|
||||
export function mergeRefs<T>(refs: (Ref<T> | undefined)[]): Ref<T> {
|
||||
return (value: T | null) => {
|
||||
const cleanups: (() => void)[] = []
|
||||
|
||||
for (const ref of refs) {
|
||||
const cleanup = assignRef(ref, value)
|
||||
const isCleanup = typeof cleanup === 'function'
|
||||
cleanups.push(isCleanup ? cleanup : () => assignRef(ref, null))
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const cleanup of cleanups) cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,7 +868,7 @@ msgstr "<0>{displayName}</0><1/><2> added you</2>"
|
||||
msgid "<0>Sign in</0><1> or </1><2>create an account</2><3> </3><4>to search for news, sports, politics, and everything else happening on Bluesky.</4>"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:265
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:250
|
||||
msgid "<0>Tap here to update your location with GPS.</0>"
|
||||
msgstr "<0>Tap here to update your location with GPS.</0>"
|
||||
|
||||
@@ -1164,7 +1164,7 @@ msgstr ""
|
||||
msgid "Add another post to thread"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:459
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:435
|
||||
msgid "Add another search filter"
|
||||
msgstr "Add another search filter"
|
||||
|
||||
@@ -1186,7 +1186,7 @@ msgstr "Add automation label to account"
|
||||
msgid "Add emoji reaction"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:467
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:443
|
||||
msgid "Add filter"
|
||||
msgstr "Add filter"
|
||||
|
||||
@@ -1285,7 +1285,7 @@ msgstr ""
|
||||
msgid "Add user to list"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:296
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:297
|
||||
msgid "Add your birthdate"
|
||||
msgstr ""
|
||||
|
||||
@@ -1361,11 +1361,6 @@ msgstr ""
|
||||
msgid "Advanced"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:76
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:270
|
||||
msgid "Advanced search"
|
||||
msgstr "Advanced search"
|
||||
|
||||
#: src/components/ageAssurance/AgeAssuranceBadge.tsx:42
|
||||
msgid "Age Assurance"
|
||||
msgstr ""
|
||||
@@ -1379,11 +1374,11 @@ msgctxt "toast"
|
||||
msgid "Age assurance inquiry was submitted"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:200
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:185
|
||||
msgid "Age assurance only takes a few minutes"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:464
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:449
|
||||
msgid "Age assurance only takes a few minutes."
|
||||
msgstr "Age assurance only takes a few minutes."
|
||||
|
||||
@@ -1425,7 +1420,7 @@ msgstr ""
|
||||
msgid "All languages will be shown in your feeds."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:277
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:245
|
||||
msgid "All of these words"
|
||||
msgstr "All of these words"
|
||||
|
||||
@@ -1703,10 +1698,12 @@ msgstr ""
|
||||
|
||||
#. Placeholder text for a date picker
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/ClearableDateField.tsx:43
|
||||
msgid "Any time"
|
||||
msgstr "Any time"
|
||||
msgid "Any date"
|
||||
msgstr "Any date"
|
||||
|
||||
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:457
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:22
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:25
|
||||
msgid "Anyone"
|
||||
msgstr ""
|
||||
|
||||
@@ -2020,19 +2017,19 @@ msgstr ""
|
||||
msgid "Banned user returning"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:183
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:184
|
||||
msgid "Based on your device's location, we think you're in <0>{geolocationString}</0>."
|
||||
msgstr "Based on your device's location, we think you're in <0>{geolocationString}</0>."
|
||||
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:251
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:236
|
||||
msgid "Based on your device's location, we think you're in <0>{region}</0>."
|
||||
msgstr "Based on your device's location, we think you're in <0>{region}</0>."
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:193
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:194
|
||||
msgid "Based on your network, we think you're in <0>{geolocationString}</0>. This estimate may be inaccurate if you're using a VPN."
|
||||
msgstr "Based on your network, we think you're in <0>{geolocationString}</0>. This estimate may be inaccurate if you're using a VPN."
|
||||
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:256
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:241
|
||||
msgid "Based on your network, we think you're in <0>{region}</0>. This estimate may be inaccurate if you're using a VPN."
|
||||
msgstr "Based on your network, we think you're in <0>{region}</0>. This estimate may be inaccurate if you're using a VPN."
|
||||
|
||||
@@ -2462,8 +2459,8 @@ msgstr "Camera access needed"
|
||||
#: src/screens/Messages/ConversationSettings/prompts.tsx:180
|
||||
#: src/screens/Profile/Header/EditProfileDialog.tsx:215
|
||||
#: src/screens/Profile/Header/EditProfileDialog.tsx:223
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:234
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:241
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:202
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:209
|
||||
#: src/screens/Settings/AppIconSettings/index.tsx:42
|
||||
#: src/screens/Settings/AppIconSettings/index.tsx:228
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:80
|
||||
@@ -2493,7 +2490,7 @@ msgstr ""
|
||||
msgid "Cancel reply"
|
||||
msgstr "Cancel reply"
|
||||
|
||||
#: src/screens/Search/Shell.tsx:565
|
||||
#: src/screens/Search/Shell.tsx:583
|
||||
msgid "Cancel search"
|
||||
msgstr ""
|
||||
|
||||
@@ -2525,8 +2522,8 @@ msgstr "carousel"
|
||||
msgid "Cashtag {tag}"
|
||||
msgstr ""
|
||||
|
||||
#. Advanced search: Example of an “all of these words” search
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:159
|
||||
#. Advanced search: Example of an “all of these words” search. Paired with “cows pigs”.
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:250
|
||||
msgid "cats dogs"
|
||||
msgstr "cats dogs"
|
||||
|
||||
@@ -2835,11 +2832,11 @@ msgstr "Click here to add people to this group chat"
|
||||
msgid "Click here to create or manage an invite link for this group chat"
|
||||
msgstr "Click here to create or manage an invite link for this group chat"
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:326
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:327
|
||||
msgid "Click here to delete your account"
|
||||
msgstr "Click here to delete your account"
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:317
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:318
|
||||
msgid "Click here to log out"
|
||||
msgstr ""
|
||||
|
||||
@@ -2852,8 +2849,8 @@ msgstr "Click here to resend the email"
|
||||
msgid "Click here to restart the verification process."
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:118
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:293
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:119
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:294
|
||||
msgid "Click here to update your birthdate"
|
||||
msgstr ""
|
||||
|
||||
@@ -3115,13 +3112,13 @@ msgstr "Connecting…"
|
||||
msgid "Connection issue"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:403
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:127
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:404
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:128
|
||||
#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:31
|
||||
msgid "Contact our moderation team"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:108
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:109
|
||||
msgid "Contact our support team"
|
||||
msgstr ""
|
||||
|
||||
@@ -3501,8 +3498,8 @@ msgstr "Couldn’t load GIFs"
|
||||
msgid "Country code"
|
||||
msgstr ""
|
||||
|
||||
#. Advanced search: Example of a “none of these words” search
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:163
|
||||
#. Advanced search: Example of a “none of these words” search. Paired with “cats dogs”.
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:268
|
||||
msgid "cows pigs"
|
||||
msgstr "cows pigs"
|
||||
|
||||
@@ -3884,9 +3881,9 @@ msgstr "Device failed to translate :("
|
||||
msgid "Dialog: adjust who can interact with this post"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:265
|
||||
msgid "Dialog: Set advanced search options"
|
||||
msgstr "Dialog: Set advanced search options"
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:233
|
||||
msgid "Dialog: Set search filters"
|
||||
msgstr "Dialog: Set search filters"
|
||||
|
||||
#: src/screens/Settings/AppearanceSettings.tsx:110
|
||||
msgid "Dim"
|
||||
@@ -4591,8 +4588,6 @@ msgstr ""
|
||||
msgid "Everybody can reply to this post."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:22
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:26
|
||||
#: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:168
|
||||
#: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:171
|
||||
msgid "Everyone"
|
||||
@@ -5169,11 +5164,11 @@ msgstr ""
|
||||
msgid "File saved successfully!"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:49
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:48
|
||||
msgid "Filter by author"
|
||||
msgstr "Filter by author"
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:30
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FollowingDropdown.tsx:29
|
||||
msgid "Filter by author (currently: {currentLabel})"
|
||||
msgstr "Filter by author (currently: {currentLabel})"
|
||||
|
||||
@@ -5210,6 +5205,12 @@ msgstr ""
|
||||
msgid "Filter who you receive notifications from"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:78
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:238
|
||||
msgctxt "search"
|
||||
msgid "Filters"
|
||||
msgstr "Filters"
|
||||
|
||||
#: src/screens/Onboarding/StepFinished/index.tsx:335
|
||||
msgid "Finalizing"
|
||||
msgstr ""
|
||||
@@ -5264,7 +5265,7 @@ msgstr ""
|
||||
msgid "Find people you know"
|
||||
msgstr "Find people you know"
|
||||
|
||||
#: src/screens/Search/Shell.tsx:742
|
||||
#: src/screens/Search/Shell.tsx:760
|
||||
msgid "Find posts, users, and feeds on Bluesky"
|
||||
msgstr ""
|
||||
|
||||
@@ -5490,7 +5491,7 @@ msgstr ""
|
||||
msgid "Food"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:105
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:106
|
||||
msgid "For organizational accounts, use the birthdate of the person who is responsible for the account."
|
||||
msgstr ""
|
||||
|
||||
@@ -5541,7 +5542,7 @@ msgstr "Four message bubbles representing a group chat. First message: \"Did you
|
||||
msgid "Free your feed"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:430
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:406
|
||||
#: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:159
|
||||
msgid "From"
|
||||
msgstr ""
|
||||
@@ -6007,11 +6008,11 @@ msgstr ""
|
||||
msgid "Hey there 👋"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:170
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:171
|
||||
msgid "Hey there!"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:275
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:276
|
||||
msgid "Hi there!"
|
||||
msgstr ""
|
||||
|
||||
@@ -6253,7 +6254,7 @@ msgstr ""
|
||||
msgid "If none are selected, all languages will be shown in your feeds."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:102
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:103
|
||||
msgid "If you are 18 years of age or older and want to try again, click the button below and use a different verification method if one is available in your region. If you have questions or concerns, <0>our support team can help.</0>"
|
||||
msgstr ""
|
||||
|
||||
@@ -6261,7 +6262,7 @@ msgstr ""
|
||||
msgid "If you are not yet an adult according to the laws of your country, your parent or legal guardian must read these Terms on your behalf."
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:115
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:116
|
||||
msgid "If you believe your birthdate is incorrect, you can update it by <0>clicking here</0>."
|
||||
msgstr ""
|
||||
|
||||
@@ -6396,7 +6397,7 @@ msgstr ""
|
||||
msgid "Imported on {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:278
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:279
|
||||
msgid "In order to provide an age-appropriate experience, we need to know your birthdate. This is a one-time thing, and your data will be kept private."
|
||||
msgstr ""
|
||||
|
||||
@@ -6444,7 +6445,7 @@ msgstr ""
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FilterBlock.tsx:58
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FilterBlock.tsx:75
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/FilterBlock.tsx:81
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:416
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:392
|
||||
msgid "Include"
|
||||
msgstr "Include"
|
||||
|
||||
@@ -6458,12 +6459,12 @@ msgid "Include posts and/or replies (currently: {currentLabel})"
|
||||
msgstr "Include posts and/or replies (currently: {currentLabel})"
|
||||
|
||||
#. Advanced search filter
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:329
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:305
|
||||
msgid "Include posts made since this date"
|
||||
msgstr "Include posts made since this date"
|
||||
|
||||
#. Advanced search filter
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:353
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:329
|
||||
msgid "Include posts made until this date"
|
||||
msgstr "Include posts made until this date"
|
||||
|
||||
@@ -6740,7 +6741,7 @@ msgstr ""
|
||||
msgid "Labels on your content"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:381
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:357
|
||||
msgid "Language"
|
||||
msgstr "Language"
|
||||
|
||||
@@ -6758,13 +6759,13 @@ msgstr ""
|
||||
msgid "Larger"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:458
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:194
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:443
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:179
|
||||
msgid "Last initiated {timeAgo} ago"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:456
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:192
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:441
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:177
|
||||
msgid "Last initiated just now"
|
||||
msgstr ""
|
||||
|
||||
@@ -7338,7 +7339,7 @@ msgstr "Marked all requests as read"
|
||||
msgid "Maybe later"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:399
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:375
|
||||
#: src/view/screens/Profile.tsx:236
|
||||
msgid "Media"
|
||||
msgstr ""
|
||||
@@ -8176,8 +8177,8 @@ msgstr ""
|
||||
msgid "Non-sexual Nudity"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:307
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:310
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:263
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:266
|
||||
msgid "None of these words"
|
||||
msgstr "None of these words"
|
||||
|
||||
@@ -8418,7 +8419,7 @@ msgstr "Oops, this profile doesn't exist. Try scanning another one."
|
||||
msgid "Oops!"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:64
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:66
|
||||
msgid "Open advanced search options"
|
||||
msgstr "Open advanced search options"
|
||||
|
||||
@@ -10444,11 +10445,11 @@ msgstr ""
|
||||
#: src/components/dialogs/SearchablePeopleList.tsx:667
|
||||
#: src/components/forms/SearchInput.tsx:51
|
||||
#: src/components/forms/SearchInput.tsx:53
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:250
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:256
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:218
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:224
|
||||
#: src/screens/Search/Shell.tsx:535
|
||||
#: src/screens/Search/Shell.tsx:585
|
||||
#: src/screens/Search/Shell.tsx:730
|
||||
#: src/screens/Search/Shell.tsx:603
|
||||
#: src/screens/Search/Shell.tsx:748
|
||||
#: src/view/shell/bottom-bar/BottomBar.tsx:216
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
@@ -10537,7 +10538,7 @@ msgstr ""
|
||||
msgid "Search profiles"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:280
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:248
|
||||
msgid "Search query"
|
||||
msgstr "Search query"
|
||||
|
||||
@@ -10883,7 +10884,7 @@ msgstr ""
|
||||
msgid "Set who can reply to your post"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:285
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:286
|
||||
msgid "Set your birthdate below and we'll get you back to posting and exploring in no time!"
|
||||
msgstr ""
|
||||
|
||||
@@ -10986,8 +10987,8 @@ msgid "Share"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/useVerificationFlow.ts:62
|
||||
msgid "Share age data"
|
||||
msgstr "Share age data"
|
||||
msgid "Share age range"
|
||||
msgstr "Share age range"
|
||||
|
||||
#: src/view/com/profile/ProfileMenu.tsx:549
|
||||
msgid "Share anyway"
|
||||
@@ -11044,7 +11045,8 @@ msgstr ""
|
||||
msgid "Share this feed"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/Shell.tsx:617
|
||||
#: src/screens/Search/Shell.tsx:556
|
||||
#: src/screens/Search/Shell.tsx:625
|
||||
msgid "Share this search"
|
||||
msgstr "Share this search"
|
||||
|
||||
@@ -11075,10 +11077,15 @@ msgstr ""
|
||||
msgid "Shared Preferences Tester"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:433
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:169
|
||||
msgid "Sharing your age data uses information stored on your device, and will therefore only work on this device. Alternatively, <0>you can use our trusted partner, KWS</0>, to complete your verification and enable access on all platforms."
|
||||
msgstr "Sharing your age data uses information stored on your device, and will therefore only work on this device. Alternatively, <0>you can use our trusted partner, KWS</0>, to complete your verification and enable access on all platforms."
|
||||
#. Shown next to the 'Share age range' button when on-device age verification is available. KWS is the name of a third-party verification partner.
|
||||
#: src/ageAssurance/components/DeviceSignalsNotice.tsx:25
|
||||
msgid "Sharing your age range reveals only the range associated with your Apple Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
||||
msgstr "Sharing your age range reveals only the range associated with your Apple Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
||||
|
||||
#. Shown next to the 'Share age range' button when on-device age verification is available. KWS is the name of a third-party verification partner.
|
||||
#: src/ageAssurance/components/DeviceSignalsNotice.tsx:43
|
||||
msgid "Sharing your age range reveals only the range associated with your Google Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
||||
msgstr "Sharing your age range reveals only the range associated with your Google Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
||||
|
||||
#: src/components/moderation/ContentHider.tsx:220
|
||||
#: src/components/moderation/LabelPreference.tsx:143
|
||||
@@ -11308,8 +11315,8 @@ msgstr ""
|
||||
msgid "Signed in as @{0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:323
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:326
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:299
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:302
|
||||
msgid "Since"
|
||||
msgstr "Since"
|
||||
|
||||
@@ -11801,7 +11808,7 @@ msgstr ""
|
||||
msgid "Tap for more information"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:212
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:213
|
||||
#: src/screens/Signup/StepInfo/index.tsx:326
|
||||
msgid "Tap here to update your location with GPS."
|
||||
msgstr "Tap here to update your location with GPS."
|
||||
@@ -11931,8 +11938,8 @@ msgstr ""
|
||||
msgid "Thanks, you have successfully verified your email address. You can close this dialog."
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:232
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:239
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:233
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:224
|
||||
msgid "Thanks! You're all set."
|
||||
msgstr ""
|
||||
|
||||
@@ -12347,8 +12354,8 @@ msgstr ""
|
||||
msgid "This email is already associated with your account."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:291
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:294
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:280
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:283
|
||||
msgid "This exact phrase"
|
||||
msgstr "This exact phrase"
|
||||
|
||||
@@ -12656,7 +12663,7 @@ msgstr ""
|
||||
msgid "To disable your email 2FA method, please verify your access to <0>{0}</0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:314
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:315
|
||||
msgid "To log out, <0>click here</0>. Or if you’d prefer, you can <1>delete your account</1>."
|
||||
msgstr "To log out, <0>click here</0>. Or if you’d prefer, you can <1>delete your account</1>."
|
||||
|
||||
@@ -12951,11 +12958,11 @@ msgstr ""
|
||||
msgid "Unfortunately, none of your subscribed labelers supports this report type."
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:262
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:263
|
||||
msgid "Unfortunately, the birthdate you have saved to your profile makes you too young to access Bluesky."
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:246
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:247
|
||||
msgid "Unfortunately, your declared age indicates that you are not old enough to access Bluesky in your region."
|
||||
msgstr ""
|
||||
|
||||
@@ -13105,8 +13112,8 @@ msgstr "Unsupported clipboard content"
|
||||
msgid "Unsupported video type: {mimeType}"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:347
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:350
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:323
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:326
|
||||
msgid "Until"
|
||||
msgstr "Until"
|
||||
|
||||
@@ -13150,8 +13157,8 @@ msgstr ""
|
||||
msgid "Update your email"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:207
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:267
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:208
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:252
|
||||
#: src/components/dialogs/DeviceLocationRequestDialog.tsx:41
|
||||
#: src/components/dialogs/DeviceLocationRequestDialog.tsx:106
|
||||
msgid "Update your location"
|
||||
@@ -13399,8 +13406,8 @@ msgstr ""
|
||||
msgid "Verify now"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:438
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:174
|
||||
#: src/ageAssurance/components/DeviceSignalsNotice.tsx:34
|
||||
#: src/ageAssurance/components/DeviceSignalsNotice.tsx:52
|
||||
msgid "Verify now using KWS"
|
||||
msgstr "Verify now using KWS"
|
||||
|
||||
@@ -13886,8 +13893,8 @@ msgstr "We’re so excited to have you join us!"
|
||||
msgid "We're sorry, but based on the data shared by your device, you are not old enough to access Bluesky."
|
||||
msgstr "We're sorry, but based on the data shared by your device, you are not old enough to access Bluesky."
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:226
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:233
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:227
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:218
|
||||
msgid "We're sorry, but based on your device's location, you are currently located in a region that requires age assurance."
|
||||
msgstr ""
|
||||
|
||||
@@ -13959,7 +13966,7 @@ msgid "What do you want to call your starter pack?"
|
||||
msgstr ""
|
||||
|
||||
#. Advanced search: Example of an “exact phrase” search
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:296
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:285
|
||||
msgid "what’s up"
|
||||
msgstr "what’s up"
|
||||
|
||||
@@ -14125,7 +14132,7 @@ msgstr ""
|
||||
msgid "You are a trusted verifier"
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:173
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:174
|
||||
msgid "You are accessing Bluesky from a region that legally requires us to verify your age before allowing you to access the app."
|
||||
msgstr ""
|
||||
|
||||
@@ -14150,8 +14157,8 @@ msgstr ""
|
||||
msgid "You are currently blocked from using the Go Live feature. To appeal this moderation decision, please submit the form below."
|
||||
msgstr ""
|
||||
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:399
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:123
|
||||
#: src/ageAssurance/components/NoAccessScreen.tsx:400
|
||||
#: src/components/ageAssurance/AgeAssuranceAccountCard.tsx:124
|
||||
msgid "You are currently unable to access Bluesky's Age Assurance flow. Please <0>contact our moderation team</0> if you believe this is an error."
|
||||
msgstr ""
|
||||
|
||||
@@ -14628,7 +14635,7 @@ msgstr ""
|
||||
msgid "You've reached the maximum number of requests allowed. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:450
|
||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:426
|
||||
msgid "You’ve reached the maximum of {MAX_FILTERS, plural, one {# filter} other {# filters}}. Add more values to an existing filter instead of creating new ones."
|
||||
msgstr "You’ve reached the maximum of {MAX_FILTERS, plural, one {# filter} other {# filters}}. Add more values to an existing filter instead of creating new ones."
|
||||
|
||||
|
||||
@@ -540,6 +540,24 @@ export function SearchScreenShell({
|
||||
value={filters.lang ?? ''}
|
||||
onChange={onChangeLang}
|
||||
/>
|
||||
) : showFilters && advancedSearchV2Enabled ? (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<AdvancedSearchDialog
|
||||
disabled={activeTab > 1}
|
||||
q={searchText}
|
||||
filters={filters}
|
||||
onSubmit={onSubmitAdvanced}
|
||||
/>
|
||||
<Button
|
||||
accessibilityRole="button"
|
||||
size="small"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
label={l`Share this search`}
|
||||
onPress={onShareSearch}>
|
||||
<ButtonIcon icon={ShareIcon} />
|
||||
</Button>
|
||||
</View>
|
||||
) : (
|
||||
<Layout.Header.Slot />
|
||||
)}
|
||||
@@ -558,19 +576,19 @@ export function SearchScreenShell({
|
||||
/>
|
||||
)}
|
||||
|
||||
<View style={[a.w_full, a.flex_row, a.align_stretch, a.gap_xs]}>
|
||||
<View style={[a.w_full, a.flex_row, a.align_stretch, a.gap_sm]}>
|
||||
<View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
|
||||
{showAutocomplete && (
|
||||
<Button
|
||||
label={l`Cancel search`}
|
||||
size="large"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
style={[a.px_sm]}
|
||||
onPress={onPressCancelSearch}
|
||||
hitSlop={HITSLOP_10}>
|
||||
<ButtonIcon icon={ArrowLeftIcon} />
|
||||
<ButtonIcon icon={ArrowLeftIcon} size="lg" />
|
||||
</Button>
|
||||
)}
|
||||
<View style={[a.flex_1]}>
|
||||
@@ -591,6 +609,25 @@ export function SearchScreenShell({
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{showFilters && !showHeader && advancedSearchV2Enabled ? (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<AdvancedSearchDialog
|
||||
disabled={activeTab > 1}
|
||||
q={searchText}
|
||||
filters={filters}
|
||||
onSubmit={onSubmitAdvanced}
|
||||
/>
|
||||
<Button
|
||||
accessibilityRole="button"
|
||||
size="small"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
label={l`Share this search`}
|
||||
onPress={onShareSearch}>
|
||||
<ButtonIcon icon={ShareIcon} />
|
||||
</Button>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{showFilters && !showHeader && !advancedSearchV2Enabled && (
|
||||
@@ -601,25 +638,6 @@ export function SearchScreenShell({
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{showFilters && advancedSearchV2Enabled ? (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<AdvancedSearchDialog
|
||||
q={searchText}
|
||||
filters={filters}
|
||||
onSubmit={onSubmitAdvanced}
|
||||
/>
|
||||
<Button
|
||||
accessibilityRole="button"
|
||||
size="small"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
label={l`Share this search`}
|
||||
onPress={onShareSearch}>
|
||||
<ButtonIcon icon={ShareIcon} />
|
||||
</Button>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</Layout.Center>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function ClearableDateField({
|
||||
*/
|
||||
value={active ? value : ''}
|
||||
placeholder={l({
|
||||
message: 'Any time',
|
||||
message: 'Any date',
|
||||
comment: 'Placeholder text for a date picker',
|
||||
})}
|
||||
accessibilityHint={accessibilityHint}
|
||||
|
||||
@@ -19,11 +19,10 @@ export function FollowingDropdown({
|
||||
const {t: l} = useLingui()
|
||||
|
||||
const options: {value: FollowingFilter; label: string}[] = [
|
||||
{value: 'everyone', label: l`Everyone`},
|
||||
{value: 'anyone', label: l`Anyone`},
|
||||
{value: 'following', label: l`People you follow`},
|
||||
]
|
||||
const currentLabel =
|
||||
options.find(o => o.value === value)?.label ?? l`Everyone`
|
||||
const currentLabel = options.find(o => o.value === value)?.label ?? l`Anyone`
|
||||
|
||||
return (
|
||||
<Menu.Root>
|
||||
|
||||
@@ -14,7 +14,7 @@ const emptySerializeState = {
|
||||
language: '',
|
||||
replies: 'all' as const,
|
||||
media: 'all' as const,
|
||||
following: 'everyone' as const,
|
||||
following: 'anyone' as const,
|
||||
dateSince: '',
|
||||
dateSinceActive: false,
|
||||
dateUntil: '',
|
||||
@@ -145,10 +145,10 @@ describe(`AdvancedSearchDialog serialize/parse`, () => {
|
||||
expect(out.filters.following).toBe('true')
|
||||
})
|
||||
|
||||
it(`leaves the following param unset for everyone`, () => {
|
||||
it(`leaves the following param unset for anyone`, () => {
|
||||
const out = serializeAdvancedSearch({
|
||||
...emptySerializeState,
|
||||
following: 'everyone',
|
||||
following: 'anyone',
|
||||
})
|
||||
expect(out.filters.following).toBeUndefined()
|
||||
})
|
||||
@@ -157,7 +157,7 @@ describe(`AdvancedSearchDialog serialize/parse`, () => {
|
||||
expect(parseAdvancedSearch('', {following: 'true'}).following).toBe(
|
||||
'following',
|
||||
)
|
||||
expect(parseAdvancedSearch('', {}).following).toBe('everyone')
|
||||
expect(parseAdvancedSearch('', {}).following).toBe('anyone')
|
||||
})
|
||||
|
||||
it(`strips redundant markers from filter values on serialize`, () => {
|
||||
@@ -177,7 +177,7 @@ describe(`AdvancedSearchDialog serialize/parse`, () => {
|
||||
language: '',
|
||||
replies: 'all',
|
||||
media: 'all',
|
||||
following: 'everyone',
|
||||
following: 'anyone',
|
||||
dateSince: '',
|
||||
dateSinceActive: false,
|
||||
dateUntil: '',
|
||||
|
||||
@@ -38,17 +38,18 @@ import {
|
||||
const MAX_FILTERS = 20
|
||||
|
||||
export function AdvancedSearchDialog({
|
||||
disabled,
|
||||
q,
|
||||
filters,
|
||||
onSubmit,
|
||||
}: {
|
||||
disabled: boolean
|
||||
q: string
|
||||
filters: SearchFilters
|
||||
onSubmit: (q: string, filters: SearchFilters) => void
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
const {t: l} = useLingui()
|
||||
const t = useTheme()
|
||||
const control = Dialog.useDialogControl()
|
||||
const filtersActive = hasActiveFilters(filters)
|
||||
const stateKey = useMemo(
|
||||
@@ -61,9 +62,10 @@ export function AdvancedSearchDialog({
|
||||
<>
|
||||
<View style={[a.relative]}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
label={l`Open advanced search options`}
|
||||
size="small"
|
||||
color="secondary"
|
||||
color={filtersActive ? 'primary_subtle' : 'secondary'}
|
||||
style={native([a.py_sm, a.px_sm])}
|
||||
onPress={() => {
|
||||
ax.metric('search:advanced:press', {
|
||||
@@ -73,25 +75,9 @@ export function AdvancedSearchDialog({
|
||||
}}>
|
||||
<ButtonIcon icon={SettingsSliderIcon} />
|
||||
<ButtonText>
|
||||
<Trans>Advanced search</Trans>
|
||||
<Trans context="search">Filters</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
{filtersActive && (
|
||||
<View
|
||||
accessible={false}
|
||||
style={[
|
||||
a.absolute,
|
||||
a.rounded_full,
|
||||
{
|
||||
top: -2,
|
||||
right: -2,
|
||||
width: 10,
|
||||
height: 10,
|
||||
backgroundColor: t.palette.primary_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||
@@ -154,24 +140,6 @@ function DialogInner({
|
||||
const scrollRef = useRef<ScrollView>(null)
|
||||
const filtersSectionRef = useRef<View>(null)
|
||||
|
||||
const suggestions = [
|
||||
{
|
||||
all: l({
|
||||
message: 'cats dogs',
|
||||
comment: 'Advanced search: Example of an “all of these words” search',
|
||||
}),
|
||||
none: l({
|
||||
message: 'cows pigs',
|
||||
comment: 'Advanced search: Example of a “none of these words” search',
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
// eslint-disable-next-line react/hook-use-state
|
||||
const [suggestion] = useState(() =>
|
||||
Math.floor(Math.random() * suggestions.length),
|
||||
)
|
||||
|
||||
function addFilter() {
|
||||
if (filters.length >= MAX_FILTERS) return
|
||||
/*
|
||||
@@ -262,12 +230,12 @@ function DialogInner({
|
||||
return (
|
||||
<Dialog.ScrollableInner
|
||||
ref={scrollRef}
|
||||
label={l`Dialog: Set advanced search options`}
|
||||
label={l`Dialog: Set search filters`}
|
||||
contentContainerStyle={[a.px_0, a.pt_0]}
|
||||
header={
|
||||
<Dialog.Header renderLeft={cancelButton} renderRight={searchButton}>
|
||||
<Dialog.HeaderText>
|
||||
<Trans>Advanced search</Trans>
|
||||
<Trans context="search">Filters</Trans>
|
||||
</Dialog.HeaderText>
|
||||
</Dialog.Header>
|
||||
}>
|
||||
@@ -279,13 +247,34 @@ function DialogInner({
|
||||
<ClearableInput
|
||||
label={l`Search query`}
|
||||
defaultValue={query}
|
||||
placeholder={suggestions[suggestion].all}
|
||||
placeholder={l({
|
||||
message: 'cats dogs',
|
||||
comment:
|
||||
'Advanced search: Example of an “all of these words” search. Paired with “cows pigs”.',
|
||||
})}
|
||||
onChangeText={setQuery}
|
||||
onSubmitEditing={handlePressSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={[twoColumn ? a.flex_row : a.flex_col, a.gap_xl]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<TextField.LabelText>
|
||||
<Trans>None of these words</Trans>
|
||||
</TextField.LabelText>
|
||||
<ClearableInput
|
||||
label={l`None of these words`}
|
||||
defaultValue={negatedWords}
|
||||
placeholder={l({
|
||||
message: 'cows pigs',
|
||||
comment:
|
||||
'Advanced search: Example of a “none of these words” search. Paired with “cats dogs”.',
|
||||
})}
|
||||
onChangeText={setNegatedWords}
|
||||
onSubmitEditing={handlePressSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={[a.flex_1]}>
|
||||
<TextField.LabelText>
|
||||
<Trans>This exact phrase</Trans>
|
||||
@@ -301,19 +290,6 @@ function DialogInner({
|
||||
onSubmitEditing={handlePressSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={[a.flex_1]}>
|
||||
<TextField.LabelText>
|
||||
<Trans>None of these words</Trans>
|
||||
</TextField.LabelText>
|
||||
<ClearableInput
|
||||
label={l`None of these words`}
|
||||
defaultValue={negatedWords}
|
||||
placeholder={suggestions[suggestion].none}
|
||||
onChangeText={setNegatedWords}
|
||||
onSubmitEditing={handlePressSearch}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
|
||||
@@ -15,9 +15,9 @@ export type MediaFilter = 'all' | 'media' | 'video'
|
||||
|
||||
/**
|
||||
* Whether to limit results to authors the user follows. Serializes into the
|
||||
* `following` sibling param ('following' -> following:true, everyone -> unset).
|
||||
* `following` sibling param ('following' -> following:true, anyone -> unset).
|
||||
*/
|
||||
export type FollowingFilter = 'everyone' | 'following'
|
||||
export type FollowingFilter = 'anyone' | 'following'
|
||||
|
||||
export type FilterField = 'authors' | 'mentions' | 'domains' | 'urls' | 'tags'
|
||||
|
||||
@@ -260,7 +260,7 @@ export function parseAdvancedSearch(
|
||||
language: lang,
|
||||
replies,
|
||||
media,
|
||||
following: filters.following === 'true' ? 'following' : 'everyone',
|
||||
following: filters.following === 'true' ? 'following' : 'anyone',
|
||||
since: since && isValidDate(since) ? since : '',
|
||||
until: until && isValidDate(until) ? until : '',
|
||||
filters: filterRows,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||
import {ALL_FORMATS, BlobSource, Input} from 'mediabunny'
|
||||
|
||||
import {loadMediaBunny} from '#/lib/media/video/mediabunny'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export function hasWebCodecs(): boolean {
|
||||
@@ -55,6 +55,7 @@ async function getMetadataWithWebCodecs(
|
||||
file: File,
|
||||
blobUrl: string,
|
||||
): Promise<ImagePickerAsset> {
|
||||
const {ALL_FORMATS, BlobSource, Input} = await loadMediaBunny()
|
||||
const input = new Input({
|
||||
source: new BlobSource(file),
|
||||
formats: ALL_FORMATS,
|
||||
@@ -72,6 +73,7 @@ async function getMetadataWithWebCodecs(
|
||||
|
||||
return {
|
||||
uri: blobUrl,
|
||||
file,
|
||||
mimeType: file.type,
|
||||
width: videoTrack.displayWidth,
|
||||
height: videoTrack.displayHeight,
|
||||
@@ -92,6 +94,7 @@ async function getMetadataWithBrowserAPIs(
|
||||
img.onload = () => {
|
||||
resolve({
|
||||
uri: blobUrl,
|
||||
file,
|
||||
mimeType: 'image/gif',
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
@@ -111,6 +114,7 @@ async function getMetadataWithBrowserAPIs(
|
||||
video.onloadedmetadata = () => {
|
||||
resolve({
|
||||
uri: blobUrl,
|
||||
file,
|
||||
mimeType: file.type,
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight,
|
||||
|
||||
Reference in New Issue
Block a user