Bluesky app fork with some witchin' additions 💫

Merge with social-app upstream again

+2960 -3117
-127
.eslintrc.js
··· 1 - module.exports = { 2 - root: true, 3 - extends: [ 4 - '@react-native', 5 - 'plugin:react/recommended', 6 - 'plugin:react/jsx-runtime', 7 - 'plugin:react-native-a11y/ios', 8 - 'prettier', 9 - ], 10 - parser: '@typescript-eslint/parser', 11 - plugins: [ 12 - '@typescript-eslint', 13 - 'react', 14 - 'lingui', 15 - 'simple-import-sort', 16 - 'bsky-internal', 17 - 'eslint-plugin-react-compiler', 18 - 'import', 19 - ], 20 - rules: { 21 - 'react/no-unescaped-entities': 0, 22 - 'react/prop-types': 0, 23 - 'react-native/no-inline-styles': 0, 24 - 'bsky-internal/avoid-unwrapped-text': [ 25 - 'error', 26 - { 27 - impliedTextComponents: [ 28 - 'H1', 29 - 'H2', 30 - 'H3', 31 - 'H4', 32 - 'H5', 33 - 'H6', 34 - 'P', 35 - 'Admonition', 36 - 'Admonition.Admonition', 37 - 'Toast.Action', 38 - 'toast.Action', 39 - 'AgeAssuranceAdmonition', 40 - 'Span', 41 - 'StackedButton', 42 - ], 43 - impliedTextProps: [], 44 - suggestedTextWrappers: { 45 - Button: 'ButtonText', 46 - 'ToggleButton.Button': 'ToggleButton.ButtonText', 47 - 'SegmentedControl.Item': 'SegmentedControl.ItemText', 48 - }, 49 - }, 50 - ], 51 - 'bsky-internal/use-exact-imports': 'error', 52 - 'bsky-internal/use-typed-gates': 'error', 53 - 'bsky-internal/use-prefixed-imports': 'error', 54 - 'simple-import-sort/imports': [ 55 - 'error', 56 - { 57 - groups: [ 58 - // Side effect imports. 59 - ['^\\u0000'], 60 - // Node.js builtins prefixed with `node:`. 61 - ['^node:'], 62 - // Packages. 63 - // Things that start with a letter (or digit or underscore), or `@` followed by a letter. 64 - // React/React Native priortized, followed by expo 65 - // Followed by all packages excluding unprefixed relative ones 66 - [ 67 - '^(react\\/(.*)$)|^(react$)|^(react-native(.*)$)', 68 - '^(expo(.*)$)|^(expo$)', 69 - '^(?!(?:alf|components|lib|locale|logger|platform|screens|state|view)(?:$|\\/))@?\\w', 70 - ], 71 - // Relative imports. 72 - // Ideally, anything that starts with a dot or # 73 - // due to unprefixed relative imports being used, we whitelist the relative paths we use 74 - // (?:$|\\/) matches end of string or / 75 - [ 76 - '^(?:#\\/)?(?:lib|state|logger|platform|locale)(?:$|\\/)', 77 - '^(?:#\\/)?view(?:$|\\/)', 78 - '^(?:#\\/)?screens(?:$|\\/)', 79 - '^(?:#\\/)?alf(?:$|\\/)', 80 - '^(?:#\\/)?components(?:$|\\/)', 81 - '^#\\/', 82 - '^\\.', 83 - ], 84 - // anything else - hopefully we don't have any of these 85 - ['^'], 86 - ], 87 - }, 88 - ], 89 - 'simple-import-sort/exports': 'error', 90 - 'react-compiler/react-compiler': 'warn', 91 - 'no-unused-vars': 'off', 92 - '@typescript-eslint/no-unused-vars': [ 93 - 'error', 94 - {argsIgnorePattern: '^_', varsIgnorePattern: '^_.+'}, 95 - ], 96 - '@typescript-eslint/consistent-type-imports': [ 97 - 'warn', 98 - {prefer: 'type-imports', fixStyle: 'inline-type-imports'}, 99 - ], 100 - 'import/consistent-type-specifier-style': ['warn', 'prefer-inline'], 101 - }, 102 - ignorePatterns: [ 103 - '**/__mocks__/*.ts', 104 - 'src/platform/polyfills.ts', 105 - 'src/third-party', 106 - 'ios', 107 - 'android', 108 - 'coverage', 109 - '*.lock', 110 - '.husky', 111 - 'patches', 112 - '*.html', 113 - 'bskyweb', 114 - 'bskyembed', 115 - 'src/locale/locales/_build/', 116 - 'src/locale/locales/**/*.js', 117 - '*.e2e.ts', 118 - '*.e2e.tsx', 119 - ], 120 - settings: { 121 - componentWrapperFunctions: ['observer'], 122 - }, 123 - parserOptions: { 124 - sourceType: 'module', 125 - ecmaVersion: 'latest', 126 - }, 127 - }
···
+2 -2
CLAUDE.md
··· 439 440 Platform detection: 441 ```tsx 442 - import {isWeb, isNative, isIOS, isAndroid} from '#/platform/detection' 443 444 - if (isNative) { 445 // Native-specific logic 446 } 447 ```
··· 439 440 Platform detection: 441 ```tsx 442 + import {IS_WEB, IS_NATIVE, IS_IOS, IS_ANDROID} from '#/env' 443 444 + if (IS_NATIVE) { 445 // Native-specific logic 446 } 447 ```
-22
bskyembed/.eslintrc.cjs
··· 1 - module.exports = { 2 - root: true, 3 - parser: '@typescript-eslint/parser', 4 - plugins: ['@typescript-eslint', 'simple-import-sort'], 5 - extends: [ 6 - 'eslint:recommended', 7 - 'preact', 8 - 'plugin:@typescript-eslint/recommended', 9 - 'plugin:@typescript-eslint/recommended-requiring-type-checking', 10 - ], 11 - rules: { 12 - 'simple-import-sort/imports': 'warn', 13 - 'simple-import-sort/exports': 'warn', 14 - 'no-else-return': 'off', 15 - }, 16 - parserOptions: { 17 - sourceType: 'module', 18 - ecmaVersion: 'latest', 19 - project: ['./tsconfig.json'], 20 - tsconfigRootDir: __dirname, 21 - }, 22 - }
···
+52
bskyembed/eslint.config.mjs
···
··· 1 + // @ts-check 2 + import js from '@eslint/js' 3 + import tseslint from 'typescript-eslint' 4 + import simpleImportSort from 'eslint-plugin-simple-import-sort' 5 + import globals from 'globals' 6 + 7 + export default tseslint.config( 8 + // Global ignores 9 + { 10 + ignores: ['dist/**', 'node_modules/**'], 11 + }, 12 + 13 + // Base JS recommended rules 14 + js.configs.recommended, 15 + 16 + // TypeScript rules with type checking 17 + ...tseslint.configs.recommendedTypeChecked, 18 + 19 + // Main configuration 20 + { 21 + files: ['**/*.{js,jsx,ts,tsx}'], 22 + plugins: { 23 + 'simple-import-sort': simpleImportSort, 24 + }, 25 + languageOptions: { 26 + ecmaVersion: 'latest', 27 + sourceType: 'module', 28 + globals: { 29 + ...globals.browser, 30 + }, 31 + parserOptions: { 32 + projectService: true, 33 + tsconfigRootDir: import.meta.dirname, 34 + }, 35 + }, 36 + rules: { 37 + 'simple-import-sort/imports': 'warn', 38 + 'simple-import-sort/exports': 'warn', 39 + 'no-else-return': 'off', 40 + '@typescript-eslint/no-require-imports': 'off', 41 + '@typescript-eslint/no-unused-vars': [ 42 + 'error', 43 + { 44 + argsIgnorePattern: '^_', 45 + varsIgnorePattern: '^_.+', 46 + caughtErrors: 'none', 47 + ignoreRestSiblings: true, 48 + }, 49 + ], 50 + }, 51 + }, 52 + )
+6 -4
bskyembed/package.json
··· 7 "dev-snippet": "tsc --project tsconfig.snippet.json && serve -s dist -p 3000 -n", 8 "build": "tsc && vite build", 9 "build-snippet": "tsc --project tsconfig.snippet.json", 10 - "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src", 11 "typecheck": "tsc --noEmit" 12 }, 13 "dependencies": { ··· 17 "devDependencies": { 18 "@preact/preset-vite": "^2.10.2", 19 "@vitejs/plugin-legacy": "^7.0.0", 20 "autoprefixer": "^10.4.19", 21 - "eslint": "^8.19.0", 22 - "eslint-config-preact": "^1.3.0", 23 - "eslint-plugin-simple-import-sort": "^12.0.0", 24 "postcss": "^8.4.38", 25 "serve": "^14.2.5", 26 "tailwindcss": "^3.4.3", 27 "terser": "^5.43.1",
··· 7 "dev-snippet": "tsc --project tsconfig.snippet.json && serve -s dist -p 3000 -n", 8 "build": "tsc && vite build", 9 "build-snippet": "tsc --project tsconfig.snippet.json", 10 + "lint": "eslint --cache src", 11 "typecheck": "tsc --noEmit" 12 }, 13 "dependencies": { ··· 17 "devDependencies": { 18 "@preact/preset-vite": "^2.10.2", 19 "@vitejs/plugin-legacy": "^7.0.0", 20 + "@eslint/js": "^9.18.0", 21 "autoprefixer": "^10.4.19", 22 + "eslint": "^9.18.0", 23 + "eslint-plugin-simple-import-sort": "^12.1.1", 24 + "globals": "^15.14.0", 25 "postcss": "^8.4.38", 26 + "typescript-eslint": "^8.20.0", 27 "serve": "^14.2.5", 28 "tailwindcss": "^3.4.3", 29 "terser": "^5.43.1",
+303 -1345
bskyembed/yarn.lock
··· 68 "@atproto/lexicon" "^0.4.12" 69 zod "^3.23.8" 70 71 - "@babel/code-frame@^7.24.2", "@babel/code-frame@^7.27.1": 72 version "7.27.1" 73 resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" 74 integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== ··· 82 resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" 83 integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== 84 85 - "@babel/core@^7.13.16": 86 - version "7.24.4" 87 - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" 88 - integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== 89 - dependencies: 90 - "@ampproject/remapping" "^2.2.0" 91 - "@babel/code-frame" "^7.24.2" 92 - "@babel/generator" "^7.24.4" 93 - "@babel/helper-compilation-targets" "^7.23.6" 94 - "@babel/helper-module-transforms" "^7.23.3" 95 - "@babel/helpers" "^7.24.4" 96 - "@babel/parser" "^7.24.4" 97 - "@babel/template" "^7.24.0" 98 - "@babel/traverse" "^7.24.1" 99 - "@babel/types" "^7.24.0" 100 - convert-source-map "^2.0.0" 101 - debug "^4.1.0" 102 - gensync "^1.0.0-beta.2" 103 - json5 "^2.2.3" 104 - semver "^6.3.1" 105 - 106 "@babel/core@^7.22.1", "@babel/core@^7.27.4": 107 version "7.28.0" 108 resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" ··· 124 json5 "^2.2.3" 125 semver "^6.3.1" 126 127 - "@babel/eslint-parser@^7.13.14": 128 - version "7.24.1" 129 - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz#e27eee93ed1d271637165ef3a86e2b9332395c32" 130 - integrity sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ== 131 - dependencies: 132 - "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" 133 - eslint-visitor-keys "^2.1.0" 134 - semver "^6.3.1" 135 - 136 - "@babel/generator@^7.24.4", "@babel/generator@^7.28.0": 137 version "7.28.0" 138 resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" 139 integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== ··· 151 dependencies: 152 "@babel/types" "^7.27.3" 153 154 - "@babel/helper-compilation-targets@^7.23.6", "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": 155 version "7.27.2" 156 resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" 157 integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== ··· 216 "@babel/traverse" "^7.27.1" 217 "@babel/types" "^7.27.1" 218 219 - "@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.27.3": 220 version "7.27.3" 221 resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02" 222 integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== ··· 232 dependencies: 233 "@babel/types" "^7.27.1" 234 235 - "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.27.1": 236 version "7.27.1" 237 resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" 238 integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== 239 240 - "@babel/helper-plugin-utils@^7.12.13": 241 - version "7.24.0" 242 - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" 243 - integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== 244 - 245 "@babel/helper-remap-async-to-generator@^7.27.1": 246 version "7.27.1" 247 resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" ··· 292 "@babel/traverse" "^7.27.1" 293 "@babel/types" "^7.27.1" 294 295 - "@babel/helpers@^7.24.4", "@babel/helpers@^7.27.6": 296 version "7.27.6" 297 resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c" 298 integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== ··· 300 "@babel/template" "^7.27.2" 301 "@babel/types" "^7.27.6" 302 303 - "@babel/parser@^7.24.4", "@babel/parser@^7.27.2", "@babel/parser@^7.28.0": 304 version "7.28.0" 305 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" 306 integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== ··· 351 resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" 352 integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== 353 354 - "@babel/plugin-syntax-class-properties@^7.12.13": 355 - version "7.12.13" 356 - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" 357 - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== 358 - dependencies: 359 - "@babel/helper-plugin-utils" "^7.12.13" 360 - 361 - "@babel/plugin-syntax-decorators@^7.12.13": 362 - version "7.24.1" 363 - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.1.tgz#71d9ad06063a6ac5430db126b5df48c70ee885fa" 364 - integrity sha512-05RJdO/cCrtVWuAaSn1tS3bH8jbsJa/Y1uD186u6J4C/1mnHFxseeuWpsqr9anvo7TUulev7tm7GDwRV+VuhDw== 365 - dependencies: 366 - "@babel/helper-plugin-utils" "^7.24.0" 367 - 368 "@babel/plugin-syntax-import-assertions@^7.27.1": 369 version "7.27.1" 370 resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" ··· 378 integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== 379 dependencies: 380 "@babel/helper-plugin-utils" "^7.27.1" 381 - 382 - "@babel/plugin-syntax-jsx@^7.12.13": 383 - version "7.24.1" 384 - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" 385 - integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== 386 - dependencies: 387 - "@babel/helper-plugin-utils" "^7.24.0" 388 389 "@babel/plugin-syntax-jsx@^7.27.1": 390 version "7.27.1" ··· 901 "@babel/types" "^7.4.4" 902 esutils "^2.0.2" 903 904 - "@babel/template@^7.24.0", "@babel/template@^7.27.1", "@babel/template@^7.27.2": 905 version "7.27.2" 906 resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" 907 integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== ··· 910 "@babel/parser" "^7.27.2" 911 "@babel/types" "^7.27.1" 912 913 - "@babel/traverse@^7.24.1", "@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": 914 version "7.28.0" 915 resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" 916 integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== ··· 923 "@babel/types" "^7.28.0" 924 debug "^4.3.1" 925 926 - "@babel/types@^7.24.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.27.6", "@babel/types@^7.28.0", "@babel/types@^7.4.4": 927 version "7.28.1" 928 resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.1.tgz#2aaf3c10b31ba03a77ac84f52b3912a0edef4cf9" 929 integrity sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ== ··· 1061 resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz#4276edd5c105bc28b11c6a1f76fb9d29d1bd25c1" 1062 integrity sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA== 1063 1064 - "@eslint-community/eslint-utils@^4.2.0": 1065 - version "4.4.0" 1066 - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" 1067 - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== 1068 dependencies: 1069 - eslint-visitor-keys "^3.3.0" 1070 1071 - "@eslint-community/regexpp@^4.6.1": 1072 - version "4.10.0" 1073 - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" 1074 - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== 1075 1076 - "@eslint/eslintrc@^2.1.4": 1077 - version "2.1.4" 1078 - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" 1079 - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== 1080 dependencies: 1081 ajv "^6.12.4" 1082 debug "^4.3.2" 1083 - espree "^9.6.0" 1084 - globals "^13.19.0" 1085 ignore "^5.2.0" 1086 import-fresh "^3.2.1" 1087 - js-yaml "^4.1.0" 1088 minimatch "^3.1.2" 1089 strip-json-comments "^3.1.1" 1090 1091 - "@eslint/js@8.57.0": 1092 - version "8.57.0" 1093 - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" 1094 - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== 1095 1096 - "@humanwhocodes/config-array@^0.11.14": 1097 - version "0.11.14" 1098 - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" 1099 - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== 1100 dependencies: 1101 - "@humanwhocodes/object-schema" "^2.0.2" 1102 - debug "^4.3.1" 1103 - minimatch "^3.0.5" 1104 1105 "@humanwhocodes/module-importer@^1.0.1": 1106 version "1.0.1" 1107 resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" 1108 integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== 1109 1110 - "@humanwhocodes/object-schema@^2.0.2": 1111 - version "2.0.3" 1112 - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" 1113 - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== 1114 1115 "@isaacs/cliui@^8.0.2": 1116 version "8.0.2" ··· 1172 "@jridgewell/resolve-uri" "^3.1.0" 1173 "@jridgewell/sourcemap-codec" "^1.4.14" 1174 1175 - "@mdn/browser-compat-data@^5.2.34", "@mdn/browser-compat-data@^5.3.13": 1176 - version "5.5.19" 1177 - resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.5.19.tgz#5c661edd669ee990dbdf2e1a8ee3c9c1c6fa7117" 1178 - integrity sha512-ntKBZtwWCy4XvJosdTJKqIMdmzgbxjopfoiMxgpzsml3dXqA7MIHCE/amidfQc06a6KvmMrpiVuYHIBt2feDog== 1179 - 1180 - "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": 1181 - version "5.1.1-v1" 1182 - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" 1183 - integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== 1184 - dependencies: 1185 - eslint-scope "5.1.1" 1186 - 1187 "@nodelib/fs.scandir@2.1.5": 1188 version "2.1.5" 1189 resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" ··· 1197 resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 1198 integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 1199 1200 - "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": 1201 version "1.2.8" 1202 resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 1203 integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== ··· 1358 resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.0.tgz#0a7eecae41f463d6591c8fecd7a5c5087345ee36" 1359 integrity sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA== 1360 1361 - "@types/estree@1.0.8": 1362 version "1.0.8" 1363 resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" 1364 integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== 1365 1366 - "@types/json-schema@^7.0.9": 1367 version "7.0.15" 1368 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" 1369 integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== 1370 1371 - "@types/semver@^7.3.12": 1372 - version "7.5.8" 1373 - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" 1374 - integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== 1375 1376 - "@typescript-eslint/experimental-utils@^5.0.0": 1377 - version "5.62.0" 1378 - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741" 1379 - integrity sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw== 1380 dependencies: 1381 - "@typescript-eslint/utils" "5.62.0" 1382 1383 - "@typescript-eslint/scope-manager@5.62.0": 1384 - version "5.62.0" 1385 - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" 1386 - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== 1387 dependencies: 1388 - "@typescript-eslint/types" "5.62.0" 1389 - "@typescript-eslint/visitor-keys" "5.62.0" 1390 1391 - "@typescript-eslint/types@5.62.0": 1392 - version "5.62.0" 1393 - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" 1394 - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== 1395 1396 - "@typescript-eslint/typescript-estree@5.62.0": 1397 - version "5.62.0" 1398 - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" 1399 - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== 1400 dependencies: 1401 - "@typescript-eslint/types" "5.62.0" 1402 - "@typescript-eslint/visitor-keys" "5.62.0" 1403 - debug "^4.3.4" 1404 - globby "^11.1.0" 1405 - is-glob "^4.0.3" 1406 - semver "^7.3.7" 1407 - tsutils "^3.21.0" 1408 1409 - "@typescript-eslint/utils@5.62.0": 1410 - version "5.62.0" 1411 - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" 1412 - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== 1413 dependencies: 1414 - "@eslint-community/eslint-utils" "^4.2.0" 1415 - "@types/json-schema" "^7.0.9" 1416 - "@types/semver" "^7.3.12" 1417 - "@typescript-eslint/scope-manager" "5.62.0" 1418 - "@typescript-eslint/types" "5.62.0" 1419 - "@typescript-eslint/typescript-estree" "5.62.0" 1420 - eslint-scope "^5.1.1" 1421 - semver "^7.3.7" 1422 1423 - "@typescript-eslint/visitor-keys@5.62.0": 1424 - version "5.62.0" 1425 - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" 1426 - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== 1427 dependencies: 1428 - "@typescript-eslint/types" "5.62.0" 1429 - eslint-visitor-keys "^3.3.0" 1430 1431 - "@ungap/structured-clone@^1.2.0": 1432 - version "1.2.0" 1433 - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" 1434 - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== 1435 1436 "@vitejs/plugin-legacy@^7.0.0": 1437 version "7.0.0" ··· 1457 resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" 1458 integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== 1459 1460 - acorn@^8.14.0: 1461 version "8.15.0" 1462 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" 1463 integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== 1464 - 1465 - acorn@^8.9.0: 1466 - version "8.11.3" 1467 - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" 1468 - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== 1469 1470 ajv@8.12.0: 1471 version "8.12.0" ··· 1544 resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 1545 integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 1546 1547 - array-buffer-byte-length@^1.0.1: 1548 - version "1.0.1" 1549 - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" 1550 - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== 1551 - dependencies: 1552 - call-bind "^1.0.5" 1553 - is-array-buffer "^3.0.4" 1554 - 1555 - array-includes@^3.1.6, array-includes@^3.1.7: 1556 - version "3.1.8" 1557 - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" 1558 - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== 1559 - dependencies: 1560 - call-bind "^1.0.7" 1561 - define-properties "^1.2.1" 1562 - es-abstract "^1.23.2" 1563 - es-object-atoms "^1.0.0" 1564 - get-intrinsic "^1.2.4" 1565 - is-string "^1.0.7" 1566 - 1567 - array-union@^2.1.0: 1568 - version "2.1.0" 1569 - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 1570 - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 1571 - 1572 - array.prototype.findlast@^1.2.4: 1573 - version "1.2.5" 1574 - resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" 1575 - integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== 1576 - dependencies: 1577 - call-bind "^1.0.7" 1578 - define-properties "^1.2.1" 1579 - es-abstract "^1.23.2" 1580 - es-errors "^1.3.0" 1581 - es-object-atoms "^1.0.0" 1582 - es-shim-unscopables "^1.0.2" 1583 - 1584 - array.prototype.flat@^1.3.1: 1585 - version "1.3.2" 1586 - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" 1587 - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== 1588 - dependencies: 1589 - call-bind "^1.0.2" 1590 - define-properties "^1.2.0" 1591 - es-abstract "^1.22.1" 1592 - es-shim-unscopables "^1.0.0" 1593 - 1594 - array.prototype.flatmap@^1.3.2: 1595 - version "1.3.2" 1596 - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" 1597 - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== 1598 - dependencies: 1599 - call-bind "^1.0.2" 1600 - define-properties "^1.2.0" 1601 - es-abstract "^1.22.1" 1602 - es-shim-unscopables "^1.0.0" 1603 - 1604 - array.prototype.toreversed@^1.1.2: 1605 - version "1.1.2" 1606 - resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" 1607 - integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== 1608 - dependencies: 1609 - call-bind "^1.0.2" 1610 - define-properties "^1.2.0" 1611 - es-abstract "^1.22.1" 1612 - es-shim-unscopables "^1.0.0" 1613 - 1614 - array.prototype.tosorted@^1.1.3: 1615 - version "1.1.3" 1616 - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" 1617 - integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== 1618 - dependencies: 1619 - call-bind "^1.0.5" 1620 - define-properties "^1.2.1" 1621 - es-abstract "^1.22.3" 1622 - es-errors "^1.1.0" 1623 - es-shim-unscopables "^1.0.2" 1624 - 1625 - arraybuffer.prototype.slice@^1.0.3: 1626 - version "1.0.3" 1627 - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" 1628 - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== 1629 - dependencies: 1630 - array-buffer-byte-length "^1.0.1" 1631 - call-bind "^1.0.5" 1632 - define-properties "^1.2.1" 1633 - es-abstract "^1.22.3" 1634 - es-errors "^1.2.1" 1635 - get-intrinsic "^1.2.3" 1636 - is-array-buffer "^3.0.4" 1637 - is-shared-array-buffer "^1.0.2" 1638 - 1639 - ast-metadata-inferer@^0.8.0: 1640 - version "0.8.0" 1641 - resolved "https://registry.yarnpkg.com/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz#0f94c3425e310d8da45823ab2161142e3f134343" 1642 - integrity sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA== 1643 - dependencies: 1644 - "@mdn/browser-compat-data" "^5.2.34" 1645 - 1646 autoprefixer@^10.4.19: 1647 version "10.4.21" 1648 resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.21.tgz#77189468e7a8ad1d9a37fbc08efc9f480cf0a95d" ··· 1654 normalize-range "^0.1.2" 1655 picocolors "^1.1.1" 1656 postcss-value-parser "^4.2.0" 1657 - 1658 - available-typed-arrays@^1.0.7: 1659 - version "1.0.7" 1660 - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" 1661 - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== 1662 - dependencies: 1663 - possible-typed-array-names "^1.0.0" 1664 1665 await-lock@^2.2.2: 1666 version "2.2.2" ··· 1754 dependencies: 1755 meow "^13.0.0" 1756 1757 - browserslist@^4.21.10: 1758 - version "4.23.0" 1759 - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" 1760 - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== 1761 - dependencies: 1762 - caniuse-lite "^1.0.30001587" 1763 - electron-to-chromium "^1.4.668" 1764 - node-releases "^2.0.14" 1765 - update-browserslist-db "^1.0.13" 1766 - 1767 browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.0, browserslist@^4.25.1: 1768 version "4.25.1" 1769 resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.1.tgz#ba9e8e6f298a1d86f829c9b975e07948967bb111" ··· 1789 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 1790 integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 1791 1792 - call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: 1793 - version "1.0.7" 1794 - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" 1795 - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== 1796 - dependencies: 1797 - es-define-property "^1.0.0" 1798 - es-errors "^1.3.0" 1799 - function-bind "^1.1.2" 1800 - get-intrinsic "^1.2.4" 1801 - set-function-length "^1.2.1" 1802 - 1803 callsites@^3.0.0: 1804 version "3.1.0" 1805 resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" ··· 1815 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" 1816 integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== 1817 1818 - caniuse-lite@^1.0.30001524: 1819 - version "1.0.30001606" 1820 - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001606.tgz#b4d5f67ab0746a3b8b5b6d1f06e39c51beb39a9e" 1821 - integrity sha512-LPbwnW4vfpJId225pwjZJOgX1m9sGfbw/RKJvw/t0QhYOOaTXHvkjVGFGPpvwEzufrjvTlsULnVTxdy4/6cqkg== 1822 - 1823 - caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726: 1824 version "1.0.30001727" 1825 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" 1826 integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== ··· 1948 resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.44.0.tgz#db4fd4fa07933c1d6898c8b112a1119a9336e959" 1949 integrity sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw== 1950 1951 - cross-spawn@^7.0.0, cross-spawn@^7.0.2: 1952 version "7.0.3" 1953 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 1954 integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== ··· 1957 shebang-command "^2.0.0" 1958 which "^2.0.1" 1959 1960 - cross-spawn@^7.0.3: 1961 version "7.0.6" 1962 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" 1963 integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== ··· 1987 resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" 1988 integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== 1989 1990 - data-view-buffer@^1.0.1: 1991 - version "1.0.1" 1992 - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" 1993 - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== 1994 - dependencies: 1995 - call-bind "^1.0.6" 1996 - es-errors "^1.3.0" 1997 - is-data-view "^1.0.1" 1998 - 1999 - data-view-byte-length@^1.0.1: 2000 - version "1.0.1" 2001 - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" 2002 - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== 2003 - dependencies: 2004 - call-bind "^1.0.7" 2005 - es-errors "^1.3.0" 2006 - is-data-view "^1.0.1" 2007 - 2008 - data-view-byte-offset@^1.0.0: 2009 - version "1.0.0" 2010 - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" 2011 - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== 2012 - dependencies: 2013 - call-bind "^1.0.6" 2014 - es-errors "^1.3.0" 2015 - is-data-view "^1.0.1" 2016 - 2017 debug@2.6.9: 2018 version "2.6.9" 2019 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" ··· 2035 dependencies: 2036 ms "2.1.2" 2037 2038 deep-extend@^0.6.0: 2039 version "0.6.0" 2040 resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" ··· 2045 resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" 2046 integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== 2047 2048 - define-data-property@^1.0.1, define-data-property@^1.1.4: 2049 - version "1.1.4" 2050 - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" 2051 - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== 2052 - dependencies: 2053 - es-define-property "^1.0.0" 2054 - es-errors "^1.3.0" 2055 - gopd "^1.0.1" 2056 - 2057 - define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: 2058 - version "1.2.1" 2059 - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" 2060 - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== 2061 - dependencies: 2062 - define-data-property "^1.0.1" 2063 - has-property-descriptors "^1.0.0" 2064 - object-keys "^1.1.1" 2065 - 2066 didyoumean@^1.2.2: 2067 version "1.2.2" 2068 resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" 2069 integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== 2070 2071 - dir-glob@^3.0.1: 2072 - version "3.0.1" 2073 - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 2074 - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 2075 - dependencies: 2076 - path-type "^4.0.0" 2077 - 2078 dlv@^1.1.3: 2079 version "1.1.3" 2080 resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" 2081 integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== 2082 2083 - doctrine@^2.1.0: 2084 - version "2.1.0" 2085 - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" 2086 - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== 2087 - dependencies: 2088 - esutils "^2.0.2" 2089 - 2090 - doctrine@^3.0.0: 2091 - version "3.0.0" 2092 - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 2093 - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 2094 - dependencies: 2095 - esutils "^2.0.2" 2096 - 2097 dom-serializer@^2.0.0: 2098 version "2.0.0" 2099 resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" ··· 2129 resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" 2130 integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== 2131 2132 - electron-to-chromium@^1.4.668, electron-to-chromium@^1.5.173: 2133 version "1.5.182" 2134 resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz#4ab73104f893938acb3ab9c28d7bec170c116b3e" 2135 integrity sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA== ··· 2149 resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" 2150 integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== 2151 2152 - es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: 2153 - version "1.23.3" 2154 - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" 2155 - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== 2156 - dependencies: 2157 - array-buffer-byte-length "^1.0.1" 2158 - arraybuffer.prototype.slice "^1.0.3" 2159 - available-typed-arrays "^1.0.7" 2160 - call-bind "^1.0.7" 2161 - data-view-buffer "^1.0.1" 2162 - data-view-byte-length "^1.0.1" 2163 - data-view-byte-offset "^1.0.0" 2164 - es-define-property "^1.0.0" 2165 - es-errors "^1.3.0" 2166 - es-object-atoms "^1.0.0" 2167 - es-set-tostringtag "^2.0.3" 2168 - es-to-primitive "^1.2.1" 2169 - function.prototype.name "^1.1.6" 2170 - get-intrinsic "^1.2.4" 2171 - get-symbol-description "^1.0.2" 2172 - globalthis "^1.0.3" 2173 - gopd "^1.0.1" 2174 - has-property-descriptors "^1.0.2" 2175 - has-proto "^1.0.3" 2176 - has-symbols "^1.0.3" 2177 - hasown "^2.0.2" 2178 - internal-slot "^1.0.7" 2179 - is-array-buffer "^3.0.4" 2180 - is-callable "^1.2.7" 2181 - is-data-view "^1.0.1" 2182 - is-negative-zero "^2.0.3" 2183 - is-regex "^1.1.4" 2184 - is-shared-array-buffer "^1.0.3" 2185 - is-string "^1.0.7" 2186 - is-typed-array "^1.1.13" 2187 - is-weakref "^1.0.2" 2188 - object-inspect "^1.13.1" 2189 - object-keys "^1.1.1" 2190 - object.assign "^4.1.5" 2191 - regexp.prototype.flags "^1.5.2" 2192 - safe-array-concat "^1.1.2" 2193 - safe-regex-test "^1.0.3" 2194 - string.prototype.trim "^1.2.9" 2195 - string.prototype.trimend "^1.0.8" 2196 - string.prototype.trimstart "^1.0.8" 2197 - typed-array-buffer "^1.0.2" 2198 - typed-array-byte-length "^1.0.1" 2199 - typed-array-byte-offset "^1.0.2" 2200 - typed-array-length "^1.0.6" 2201 - unbox-primitive "^1.0.2" 2202 - which-typed-array "^1.1.15" 2203 - 2204 - es-define-property@^1.0.0: 2205 - version "1.0.0" 2206 - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" 2207 - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== 2208 - dependencies: 2209 - get-intrinsic "^1.2.4" 2210 - 2211 - es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: 2212 - version "1.3.0" 2213 - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" 2214 - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== 2215 - 2216 - es-iterator-helpers@^1.0.17: 2217 - version "1.0.18" 2218 - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" 2219 - integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== 2220 - dependencies: 2221 - call-bind "^1.0.7" 2222 - define-properties "^1.2.1" 2223 - es-abstract "^1.23.0" 2224 - es-errors "^1.3.0" 2225 - es-set-tostringtag "^2.0.3" 2226 - function-bind "^1.1.2" 2227 - get-intrinsic "^1.2.4" 2228 - globalthis "^1.0.3" 2229 - has-property-descriptors "^1.0.2" 2230 - has-proto "^1.0.3" 2231 - has-symbols "^1.0.3" 2232 - internal-slot "^1.0.7" 2233 - iterator.prototype "^1.1.2" 2234 - safe-array-concat "^1.1.2" 2235 - 2236 - es-object-atoms@^1.0.0: 2237 - version "1.0.0" 2238 - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" 2239 - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== 2240 - dependencies: 2241 - es-errors "^1.3.0" 2242 - 2243 - es-set-tostringtag@^2.0.3: 2244 - version "2.0.3" 2245 - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" 2246 - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== 2247 - dependencies: 2248 - get-intrinsic "^1.2.4" 2249 - has-tostringtag "^1.0.2" 2250 - hasown "^2.0.1" 2251 - 2252 - es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: 2253 - version "1.0.2" 2254 - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" 2255 - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== 2256 - dependencies: 2257 - hasown "^2.0.0" 2258 - 2259 - es-to-primitive@^1.2.1: 2260 - version "1.2.1" 2261 - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" 2262 - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== 2263 - dependencies: 2264 - is-callable "^1.1.4" 2265 - is-date-object "^1.0.1" 2266 - is-symbol "^1.0.2" 2267 - 2268 esbuild@^0.25.0: 2269 version "0.25.6" 2270 resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.6.tgz#9b82a3db2fa131aec069ab040fd57ed0a880cdcd" ··· 2307 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 2308 integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 2309 2310 - eslint-config-preact@^1.3.0: 2311 - version "1.3.0" 2312 - resolved "https://registry.yarnpkg.com/eslint-config-preact/-/eslint-config-preact-1.3.0.tgz#17b72813078f4d1d4d2b79938ec21f92338bc9c0" 2313 - integrity sha512-yHYXg5qNzEJd3D/30AmsIW0W8MuY858KpApXp7xxBF08IYUljSKCOqMx+dVucXHQnAm7+11wOnMkgVHIBAechw== 2314 - dependencies: 2315 - "@babel/core" "^7.13.16" 2316 - "@babel/eslint-parser" "^7.13.14" 2317 - "@babel/plugin-syntax-class-properties" "^7.12.13" 2318 - "@babel/plugin-syntax-decorators" "^7.12.13" 2319 - "@babel/plugin-syntax-jsx" "^7.12.13" 2320 - eslint-plugin-compat "^4.0.0" 2321 - eslint-plugin-jest "^25.2.4" 2322 - eslint-plugin-react "^7.27.0" 2323 - eslint-plugin-react-hooks "^4.3.0" 2324 - 2325 - eslint-plugin-compat@^4.0.0: 2326 - version "4.2.0" 2327 - resolved "https://registry.yarnpkg.com/eslint-plugin-compat/-/eslint-plugin-compat-4.2.0.tgz#eeaf80daa1afe495c88a47e9281295acae45c0aa" 2328 - integrity sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w== 2329 - dependencies: 2330 - "@mdn/browser-compat-data" "^5.3.13" 2331 - ast-metadata-inferer "^0.8.0" 2332 - browserslist "^4.21.10" 2333 - caniuse-lite "^1.0.30001524" 2334 - find-up "^5.0.0" 2335 - lodash.memoize "^4.1.2" 2336 - semver "^7.5.4" 2337 - 2338 - eslint-plugin-jest@^25.2.4: 2339 - version "25.7.0" 2340 - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" 2341 - integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== 2342 - dependencies: 2343 - "@typescript-eslint/experimental-utils" "^5.0.0" 2344 - 2345 - eslint-plugin-react-hooks@^4.3.0: 2346 - version "4.6.0" 2347 - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" 2348 - integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== 2349 - 2350 - eslint-plugin-react@^7.27.0: 2351 - version "7.34.1" 2352 - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" 2353 - integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== 2354 - dependencies: 2355 - array-includes "^3.1.7" 2356 - array.prototype.findlast "^1.2.4" 2357 - array.prototype.flatmap "^1.3.2" 2358 - array.prototype.toreversed "^1.1.2" 2359 - array.prototype.tosorted "^1.1.3" 2360 - doctrine "^2.1.0" 2361 - es-iterator-helpers "^1.0.17" 2362 - estraverse "^5.3.0" 2363 - jsx-ast-utils "^2.4.1 || ^3.0.0" 2364 - minimatch "^3.1.2" 2365 - object.entries "^1.1.7" 2366 - object.fromentries "^2.0.7" 2367 - object.hasown "^1.1.3" 2368 - object.values "^1.1.7" 2369 - prop-types "^15.8.1" 2370 - resolve "^2.0.0-next.5" 2371 - semver "^6.3.1" 2372 - string.prototype.matchall "^4.0.10" 2373 - 2374 - eslint-plugin-simple-import-sort@^12.0.0: 2375 - version "12.0.0" 2376 - resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.0.0.tgz#3cfa05d74509bd4dc329a956938823812194dbb6" 2377 - integrity sha512-8o0dVEdAkYap0Cn5kNeklaKcT1nUsa3LITWEuFk3nJifOoD+5JQGoyDUW2W/iPWwBsNBJpyJS9y4je/BgxLcyQ== 2378 - 2379 - eslint-scope@5.1.1, eslint-scope@^5.1.1: 2380 - version "5.1.1" 2381 - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" 2382 - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 2383 - dependencies: 2384 - esrecurse "^4.3.0" 2385 - estraverse "^4.1.1" 2386 2387 - eslint-scope@^7.2.2: 2388 - version "7.2.2" 2389 - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" 2390 - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== 2391 dependencies: 2392 esrecurse "^4.3.0" 2393 estraverse "^5.2.0" 2394 2395 - eslint-visitor-keys@^2.1.0: 2396 - version "2.1.0" 2397 - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" 2398 - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== 2399 - 2400 - eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: 2401 version "3.4.3" 2402 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" 2403 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== 2404 2405 - eslint@^8.19.0: 2406 - version "8.57.0" 2407 - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" 2408 - integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== 2409 dependencies: 2410 - "@eslint-community/eslint-utils" "^4.2.0" 2411 - "@eslint-community/regexpp" "^4.6.1" 2412 - "@eslint/eslintrc" "^2.1.4" 2413 - "@eslint/js" "8.57.0" 2414 - "@humanwhocodes/config-array" "^0.11.14" 2415 "@humanwhocodes/module-importer" "^1.0.1" 2416 - "@nodelib/fs.walk" "^1.2.8" 2417 - "@ungap/structured-clone" "^1.2.0" 2418 ajv "^6.12.4" 2419 chalk "^4.0.0" 2420 - cross-spawn "^7.0.2" 2421 debug "^4.3.2" 2422 - doctrine "^3.0.0" 2423 escape-string-regexp "^4.0.0" 2424 - eslint-scope "^7.2.2" 2425 - eslint-visitor-keys "^3.4.3" 2426 - espree "^9.6.1" 2427 - esquery "^1.4.2" 2428 esutils "^2.0.2" 2429 fast-deep-equal "^3.1.3" 2430 - file-entry-cache "^6.0.1" 2431 find-up "^5.0.0" 2432 glob-parent "^6.0.2" 2433 - globals "^13.19.0" 2434 - graphemer "^1.4.0" 2435 ignore "^5.2.0" 2436 imurmurhash "^0.1.4" 2437 is-glob "^4.0.0" 2438 - is-path-inside "^3.0.3" 2439 - js-yaml "^4.1.0" 2440 json-stable-stringify-without-jsonify "^1.0.1" 2441 - levn "^0.4.1" 2442 lodash.merge "^4.6.2" 2443 minimatch "^3.1.2" 2444 natural-compare "^1.4.0" 2445 optionator "^0.9.3" 2446 - strip-ansi "^6.0.1" 2447 - text-table "^0.2.0" 2448 2449 - espree@^9.6.0, espree@^9.6.1: 2450 - version "9.6.1" 2451 - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" 2452 - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== 2453 dependencies: 2454 - acorn "^8.9.0" 2455 acorn-jsx "^5.3.2" 2456 - eslint-visitor-keys "^3.4.1" 2457 2458 - esquery@^1.4.2: 2459 - version "1.5.0" 2460 - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" 2461 - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== 2462 dependencies: 2463 estraverse "^5.1.0" 2464 ··· 2469 dependencies: 2470 estraverse "^5.2.0" 2471 2472 - estraverse@^4.1.1: 2473 - version "4.3.0" 2474 - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" 2475 - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 2476 - 2477 - estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: 2478 version "5.3.0" 2479 resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" 2480 integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== ··· 2509 resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 2510 integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 2511 2512 - fast-glob@^3.2.9, fast-glob@^3.3.0: 2513 version "3.3.2" 2514 resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" 2515 integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== ··· 2542 resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.6.tgz#2b268c0232697063111bbf3f64810a2a741ba281" 2543 integrity sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== 2544 2545 - file-entry-cache@^6.0.1: 2546 - version "6.0.1" 2547 - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" 2548 - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== 2549 dependencies: 2550 - flat-cache "^3.0.4" 2551 2552 fill-range@^7.0.1: 2553 version "7.0.1" ··· 2564 locate-path "^6.0.0" 2565 path-exists "^4.0.0" 2566 2567 - flat-cache@^3.0.4: 2568 - version "3.2.0" 2569 - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" 2570 - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== 2571 dependencies: 2572 flatted "^3.2.9" 2573 - keyv "^4.5.3" 2574 - rimraf "^3.0.2" 2575 2576 flatted@^3.2.9: 2577 version "3.3.1" 2578 resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" 2579 integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== 2580 2581 - for-each@^0.3.3: 2582 - version "0.3.3" 2583 - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" 2584 - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== 2585 - dependencies: 2586 - is-callable "^1.1.3" 2587 - 2588 foreground-child@^3.1.0: 2589 version "3.1.1" 2590 resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" ··· 2598 resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" 2599 integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== 2600 2601 - fs.realpath@^1.0.0: 2602 - version "1.0.0" 2603 - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 2604 - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 2605 - 2606 fsevents@~2.3.2, fsevents@~2.3.3: 2607 version "2.3.3" 2608 resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" ··· 2613 resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" 2614 integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 2615 2616 - function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: 2617 - version "1.1.6" 2618 - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" 2619 - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== 2620 - dependencies: 2621 - call-bind "^1.0.2" 2622 - define-properties "^1.2.0" 2623 - es-abstract "^1.22.1" 2624 - functions-have-names "^1.2.3" 2625 - 2626 - functions-have-names@^1.2.3: 2627 - version "1.2.3" 2628 - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" 2629 - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== 2630 - 2631 gensync@^1.0.0-beta.2: 2632 version "1.0.0-beta.2" 2633 resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 2634 integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== 2635 2636 - get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: 2637 - version "1.2.4" 2638 - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" 2639 - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== 2640 - dependencies: 2641 - es-errors "^1.3.0" 2642 - function-bind "^1.1.2" 2643 - has-proto "^1.0.1" 2644 - has-symbols "^1.0.3" 2645 - hasown "^2.0.0" 2646 - 2647 get-stream@^6.0.0: 2648 version "6.0.1" 2649 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" 2650 integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== 2651 - 2652 - get-symbol-description@^1.0.2: 2653 - version "1.0.2" 2654 - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" 2655 - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== 2656 - dependencies: 2657 - call-bind "^1.0.5" 2658 - es-errors "^1.3.0" 2659 - get-intrinsic "^1.2.4" 2660 2661 glob-parent@^5.1.2, glob-parent@~5.1.2: 2662 version "5.1.2" ··· 2683 minipass "^7.0.4" 2684 path-scurry "^1.10.2" 2685 2686 - glob@^7.1.3: 2687 - version "7.2.3" 2688 - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" 2689 - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 2690 - dependencies: 2691 - fs.realpath "^1.0.0" 2692 - inflight "^1.0.4" 2693 - inherits "2" 2694 - minimatch "^3.1.1" 2695 - once "^1.3.0" 2696 - path-is-absolute "^1.0.0" 2697 - 2698 - globals@^13.19.0: 2699 - version "13.24.0" 2700 - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" 2701 - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== 2702 - dependencies: 2703 - type-fest "^0.20.2" 2704 - 2705 - globalthis@^1.0.3: 2706 - version "1.0.3" 2707 - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" 2708 - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== 2709 - dependencies: 2710 - define-properties "^1.1.3" 2711 2712 - globby@^11.1.0: 2713 - version "11.1.0" 2714 - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" 2715 - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== 2716 - dependencies: 2717 - array-union "^2.1.0" 2718 - dir-glob "^3.0.1" 2719 - fast-glob "^3.2.9" 2720 - ignore "^5.2.0" 2721 - merge2 "^1.4.1" 2722 - slash "^3.0.0" 2723 2724 globrex@^0.1.2: 2725 version "0.1.2" 2726 resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" 2727 integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== 2728 - 2729 - gopd@^1.0.1: 2730 - version "1.0.1" 2731 - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" 2732 - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== 2733 - dependencies: 2734 - get-intrinsic "^1.1.3" 2735 2736 graphemer@^1.4.0: 2737 version "1.4.0" 2738 resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" 2739 integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== 2740 2741 - has-bigints@^1.0.1, has-bigints@^1.0.2: 2742 - version "1.0.2" 2743 - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" 2744 - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== 2745 - 2746 has-flag@^4.0.0: 2747 version "4.0.0" 2748 resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 2749 integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 2750 2751 - has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: 2752 - version "1.0.2" 2753 - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" 2754 - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== 2755 - dependencies: 2756 - es-define-property "^1.0.0" 2757 - 2758 - has-proto@^1.0.1, has-proto@^1.0.3: 2759 - version "1.0.3" 2760 - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" 2761 - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== 2762 - 2763 - has-symbols@^1.0.2, has-symbols@^1.0.3: 2764 - version "1.0.3" 2765 - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" 2766 - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 2767 - 2768 - has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: 2769 - version "1.0.2" 2770 - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" 2771 - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== 2772 - dependencies: 2773 - has-symbols "^1.0.3" 2774 - 2775 - hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: 2776 version "2.0.2" 2777 resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" 2778 integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== ··· 2794 resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" 2795 integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== 2796 2797 import-fresh@^3.2.1: 2798 version "3.3.0" 2799 resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" ··· 2807 resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 2808 integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== 2809 2810 - inflight@^1.0.4: 2811 - version "1.0.6" 2812 - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 2813 - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 2814 - dependencies: 2815 - once "^1.3.0" 2816 - wrappy "1" 2817 - 2818 - inherits@2: 2819 - version "2.0.4" 2820 - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 2821 - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 2822 - 2823 ini@~1.3.0: 2824 version "1.3.8" 2825 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" 2826 integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== 2827 2828 - internal-slot@^1.0.7: 2829 - version "1.0.7" 2830 - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" 2831 - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== 2832 - dependencies: 2833 - es-errors "^1.3.0" 2834 - hasown "^2.0.0" 2835 - side-channel "^1.0.4" 2836 - 2837 - is-array-buffer@^3.0.4: 2838 - version "3.0.4" 2839 - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" 2840 - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== 2841 - dependencies: 2842 - call-bind "^1.0.2" 2843 - get-intrinsic "^1.2.1" 2844 - 2845 - is-async-function@^2.0.0: 2846 - version "2.0.0" 2847 - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" 2848 - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== 2849 - dependencies: 2850 - has-tostringtag "^1.0.0" 2851 - 2852 - is-bigint@^1.0.1: 2853 - version "1.0.4" 2854 - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" 2855 - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== 2856 - dependencies: 2857 - has-bigints "^1.0.1" 2858 - 2859 is-binary-path@~2.1.0: 2860 version "2.1.0" 2861 resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" ··· 2863 dependencies: 2864 binary-extensions "^2.0.0" 2865 2866 - is-boolean-object@^1.1.0: 2867 - version "1.1.2" 2868 - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" 2869 - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== 2870 - dependencies: 2871 - call-bind "^1.0.2" 2872 - has-tostringtag "^1.0.0" 2873 - 2874 - is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: 2875 - version "1.2.7" 2876 - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" 2877 - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== 2878 - 2879 is-core-module@^2.13.0: 2880 version "2.13.1" 2881 resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" ··· 2890 dependencies: 2891 hasown "^2.0.2" 2892 2893 - is-data-view@^1.0.1: 2894 - version "1.0.1" 2895 - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" 2896 - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== 2897 - dependencies: 2898 - is-typed-array "^1.1.13" 2899 - 2900 - is-date-object@^1.0.1, is-date-object@^1.0.5: 2901 - version "1.0.5" 2902 - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" 2903 - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== 2904 - dependencies: 2905 - has-tostringtag "^1.0.0" 2906 - 2907 is-docker@^2.0.0: 2908 version "2.2.1" 2909 resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" ··· 2914 resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 2915 integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 2916 2917 - is-finalizationregistry@^1.0.2: 2918 - version "1.0.2" 2919 - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" 2920 - integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== 2921 - dependencies: 2922 - call-bind "^1.0.2" 2923 - 2924 is-fullwidth-code-point@^3.0.0: 2925 version "3.0.0" 2926 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 2927 integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 2928 2929 - is-generator-function@^1.0.10: 2930 - version "1.0.10" 2931 - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" 2932 - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== 2933 - dependencies: 2934 - has-tostringtag "^1.0.0" 2935 - 2936 is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: 2937 version "4.0.3" 2938 resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" ··· 2940 dependencies: 2941 is-extglob "^2.1.1" 2942 2943 - is-map@^2.0.3: 2944 - version "2.0.3" 2945 - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" 2946 - integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== 2947 - 2948 - is-negative-zero@^2.0.3: 2949 - version "2.0.3" 2950 - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" 2951 - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== 2952 - 2953 - is-number-object@^1.0.4: 2954 - version "1.0.7" 2955 - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" 2956 - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== 2957 - dependencies: 2958 - has-tostringtag "^1.0.0" 2959 - 2960 is-number@^7.0.0: 2961 version "7.0.0" 2962 resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 2963 integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 2964 2965 - is-path-inside@^3.0.3: 2966 - version "3.0.3" 2967 - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" 2968 - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== 2969 - 2970 is-port-reachable@4.0.0: 2971 version "4.0.0" 2972 resolved "https://registry.yarnpkg.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d" 2973 integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig== 2974 2975 - is-regex@^1.1.4: 2976 - version "1.1.4" 2977 - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" 2978 - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== 2979 - dependencies: 2980 - call-bind "^1.0.2" 2981 - has-tostringtag "^1.0.0" 2982 - 2983 - is-set@^2.0.3: 2984 - version "2.0.3" 2985 - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" 2986 - integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== 2987 - 2988 - is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: 2989 - version "1.0.3" 2990 - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" 2991 - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== 2992 - dependencies: 2993 - call-bind "^1.0.7" 2994 - 2995 is-stream@^2.0.0: 2996 version "2.0.1" 2997 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" 2998 integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== 2999 3000 - is-string@^1.0.5, is-string@^1.0.7: 3001 - version "1.0.7" 3002 - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" 3003 - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== 3004 - dependencies: 3005 - has-tostringtag "^1.0.0" 3006 - 3007 - is-symbol@^1.0.2, is-symbol@^1.0.3: 3008 - version "1.0.4" 3009 - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" 3010 - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== 3011 - dependencies: 3012 - has-symbols "^1.0.2" 3013 - 3014 - is-typed-array@^1.1.13: 3015 - version "1.1.13" 3016 - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" 3017 - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== 3018 - dependencies: 3019 - which-typed-array "^1.1.14" 3020 - 3021 - is-weakmap@^2.0.2: 3022 - version "2.0.2" 3023 - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" 3024 - integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== 3025 - 3026 - is-weakref@^1.0.2: 3027 - version "1.0.2" 3028 - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" 3029 - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== 3030 - dependencies: 3031 - call-bind "^1.0.2" 3032 - 3033 - is-weakset@^2.0.3: 3034 - version "2.0.3" 3035 - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" 3036 - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== 3037 - dependencies: 3038 - call-bind "^1.0.7" 3039 - get-intrinsic "^1.2.4" 3040 - 3041 is-wsl@^2.2.0: 3042 version "2.2.0" 3043 resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" ··· 3045 dependencies: 3046 is-docker "^2.0.0" 3047 3048 - isarray@^2.0.5: 3049 - version "2.0.5" 3050 - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" 3051 - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== 3052 - 3053 isexe@^2.0.0: 3054 version "2.0.0" 3055 resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" ··· 3060 resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" 3061 integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== 3062 3063 - iterator.prototype@^1.1.2: 3064 - version "1.1.2" 3065 - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" 3066 - integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== 3067 - dependencies: 3068 - define-properties "^1.2.1" 3069 - get-intrinsic "^1.2.1" 3070 - has-symbols "^1.0.3" 3071 - reflect.getprototypeof "^1.0.4" 3072 - set-function-name "^2.0.1" 3073 - 3074 jackspeak@^2.3.6: 3075 version "2.3.6" 3076 resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" ··· 3085 resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" 3086 integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== 3087 3088 - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: 3089 version "4.0.0" 3090 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 3091 integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 3092 3093 - js-yaml@^4.1.0: 3094 - version "4.1.0" 3095 - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 3096 - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 3097 dependencies: 3098 argparse "^2.0.1" 3099 ··· 3132 resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" 3133 integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== 3134 3135 - "jsx-ast-utils@^2.4.1 || ^3.0.0": 3136 - version "3.3.5" 3137 - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" 3138 - integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== 3139 - dependencies: 3140 - array-includes "^3.1.6" 3141 - array.prototype.flat "^1.3.1" 3142 - object.assign "^4.1.4" 3143 - object.values "^1.1.6" 3144 - 3145 - keyv@^4.5.3: 3146 version "4.5.4" 3147 resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" 3148 integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== ··· 3189 resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" 3190 integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== 3191 3192 - lodash.memoize@^4.1.2: 3193 - version "4.1.2" 3194 - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" 3195 - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== 3196 - 3197 lodash.merge@^4.6.2: 3198 version "4.6.2" 3199 resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" 3200 integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 3201 3202 - loose-envify@^1.4.0: 3203 - version "1.4.0" 3204 - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 3205 - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 3206 - dependencies: 3207 - js-tokens "^3.0.0 || ^4.0.0" 3208 - 3209 lru-cache@^10.2.0: 3210 version "10.2.0" 3211 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" ··· 3218 dependencies: 3219 yallist "^3.0.2" 3220 3221 - lru-cache@^6.0.0: 3222 - version "6.0.0" 3223 - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 3224 - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 3225 - dependencies: 3226 - yallist "^4.0.0" 3227 - 3228 "magic-string@0.x >= 0.26.0", magic-string@^0.30.17: 3229 version "0.30.17" 3230 resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" ··· 3242 resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" 3243 integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== 3244 3245 - merge2@^1.3.0, merge2@^1.4.1: 3246 version "1.4.1" 3247 resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 3248 integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== ··· 3277 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 3278 integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== 3279 3280 - minimatch@3.1.2, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: 3281 version "3.1.2" 3282 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 3283 integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== ··· 3291 dependencies: 3292 brace-expansion "^2.0.1" 3293 3294 minimist@^1.2.0: 3295 version "1.2.8" 3296 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" ··· 3358 css-select "^5.1.0" 3359 he "1.2.0" 3360 3361 - node-releases@^2.0.14, node-releases@^2.0.19: 3362 version "2.0.19" 3363 resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" 3364 integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== ··· 3387 dependencies: 3388 boolbase "^1.0.0" 3389 3390 - object-assign@^4.0.1, object-assign@^4.1.1: 3391 version "4.1.1" 3392 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 3393 integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== ··· 3397 resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" 3398 integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== 3399 3400 - object-inspect@^1.13.1: 3401 - version "1.13.1" 3402 - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" 3403 - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== 3404 - 3405 - object-keys@^1.1.1: 3406 - version "1.1.1" 3407 - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 3408 - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 3409 - 3410 - object.assign@^4.1.4, object.assign@^4.1.5: 3411 - version "4.1.5" 3412 - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" 3413 - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== 3414 - dependencies: 3415 - call-bind "^1.0.5" 3416 - define-properties "^1.2.1" 3417 - has-symbols "^1.0.3" 3418 - object-keys "^1.1.1" 3419 - 3420 - object.entries@^1.1.7: 3421 - version "1.1.8" 3422 - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" 3423 - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== 3424 - dependencies: 3425 - call-bind "^1.0.7" 3426 - define-properties "^1.2.1" 3427 - es-object-atoms "^1.0.0" 3428 - 3429 - object.fromentries@^2.0.7: 3430 - version "2.0.8" 3431 - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" 3432 - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== 3433 - dependencies: 3434 - call-bind "^1.0.7" 3435 - define-properties "^1.2.1" 3436 - es-abstract "^1.23.2" 3437 - es-object-atoms "^1.0.0" 3438 - 3439 - object.hasown@^1.1.3: 3440 - version "1.1.4" 3441 - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" 3442 - integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== 3443 - dependencies: 3444 - define-properties "^1.2.1" 3445 - es-abstract "^1.23.2" 3446 - es-object-atoms "^1.0.0" 3447 - 3448 - object.values@^1.1.6, object.values@^1.1.7: 3449 - version "1.2.0" 3450 - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" 3451 - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== 3452 - dependencies: 3453 - call-bind "^1.0.7" 3454 - define-properties "^1.2.1" 3455 - es-object-atoms "^1.0.0" 3456 - 3457 on-headers@~1.1.0: 3458 version "1.1.0" 3459 resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" 3460 integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== 3461 3462 - once@^1.3.0: 3463 - version "1.4.0" 3464 - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 3465 - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 3466 - dependencies: 3467 - wrappy "1" 3468 - 3469 onetime@^5.1.2: 3470 version "5.1.2" 3471 resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" ··· 3511 resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 3512 integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 3513 3514 - path-is-absolute@^1.0.0: 3515 - version "1.0.1" 3516 - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 3517 - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 3518 - 3519 path-is-inside@1.0.2: 3520 version "1.0.2" 3521 resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" ··· 3544 resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" 3545 integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== 3546 3547 - path-type@^4.0.0: 3548 - version "4.0.0" 3549 - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 3550 - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 3551 - 3552 picocolors@^1.0.0, picocolors@^1.1.1: 3553 version "1.1.1" 3554 resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" ··· 3564 resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" 3565 integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== 3566 3567 pify@^2.3.0: 3568 version "2.3.0" 3569 resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" ··· 3573 version "4.0.6" 3574 resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" 3575 integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== 3576 - 3577 - possible-typed-array-names@^1.0.0: 3578 - version "1.0.0" 3579 - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" 3580 - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== 3581 3582 postcss-import@^15.1.0: 3583 version "15.1.0" ··· 3651 resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" 3652 integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 3653 3654 - prop-types@^15.8.1: 3655 - version "15.8.1" 3656 - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" 3657 - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== 3658 - dependencies: 3659 - loose-envify "^1.4.0" 3660 - object-assign "^4.1.1" 3661 - react-is "^16.13.1" 3662 - 3663 punycode@^2.1.0: 3664 version "2.3.1" 3665 resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" ··· 3685 minimist "^1.2.0" 3686 strip-json-comments "~2.0.1" 3687 3688 - react-is@^16.13.1: 3689 - version "16.13.1" 3690 - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 3691 - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 3692 - 3693 read-cache@^1.0.0: 3694 version "1.0.0" 3695 resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" ··· 3704 dependencies: 3705 picomatch "^2.2.1" 3706 3707 - reflect.getprototypeof@^1.0.4: 3708 - version "1.0.6" 3709 - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" 3710 - integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== 3711 - dependencies: 3712 - call-bind "^1.0.7" 3713 - define-properties "^1.2.1" 3714 - es-abstract "^1.23.1" 3715 - es-errors "^1.3.0" 3716 - get-intrinsic "^1.2.4" 3717 - globalthis "^1.0.3" 3718 - which-builtin-type "^1.1.3" 3719 - 3720 regenerate-unicode-properties@^10.2.0: 3721 version "10.2.0" 3722 resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" ··· 3733 version "0.14.1" 3734 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" 3735 integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== 3736 - 3737 - regexp.prototype.flags@^1.5.2: 3738 - version "1.5.2" 3739 - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" 3740 - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== 3741 - dependencies: 3742 - call-bind "^1.0.6" 3743 - define-properties "^1.2.1" 3744 - es-errors "^1.3.0" 3745 - set-function-name "^2.0.1" 3746 3747 regexpu-core@^6.2.0: 3748 version "6.2.0" ··· 3811 path-parse "^1.0.7" 3812 supports-preserve-symlinks-flag "^1.0.0" 3813 3814 - resolve@^2.0.0-next.5: 3815 - version "2.0.0-next.5" 3816 - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" 3817 - integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== 3818 - dependencies: 3819 - is-core-module "^2.13.0" 3820 - path-parse "^1.0.7" 3821 - supports-preserve-symlinks-flag "^1.0.0" 3822 - 3823 reusify@^1.0.4: 3824 version "1.0.4" 3825 resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 3826 integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 3827 - 3828 - rimraf@^3.0.2: 3829 - version "3.0.2" 3830 - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 3831 - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 3832 - dependencies: 3833 - glob "^7.1.3" 3834 3835 rollup@^4.40.0: 3836 version "4.45.0" ··· 3868 dependencies: 3869 queue-microtask "^1.2.2" 3870 3871 - safe-array-concat@^1.1.2: 3872 - version "1.1.2" 3873 - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" 3874 - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== 3875 - dependencies: 3876 - call-bind "^1.0.7" 3877 - get-intrinsic "^1.2.4" 3878 - has-symbols "^1.0.3" 3879 - isarray "^2.0.5" 3880 - 3881 safe-buffer@5.2.1, safe-buffer@^5.0.1: 3882 version "5.2.1" 3883 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 3884 integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 3885 3886 - safe-regex-test@^1.0.3: 3887 - version "1.0.3" 3888 - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" 3889 - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== 3890 - dependencies: 3891 - call-bind "^1.0.6" 3892 - es-errors "^1.3.0" 3893 - is-regex "^1.1.4" 3894 - 3895 semver@^6.3.1: 3896 version "6.3.1" 3897 resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" 3898 integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== 3899 3900 - semver@^7.3.7, semver@^7.5.4: 3901 - version "7.6.0" 3902 - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" 3903 - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== 3904 - dependencies: 3905 - lru-cache "^6.0.0" 3906 3907 serve-handler@6.1.6: 3908 version "6.1.6" ··· 3934 serve-handler "6.1.6" 3935 update-check "1.5.4" 3936 3937 - set-function-length@^1.2.1: 3938 - version "1.2.2" 3939 - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" 3940 - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== 3941 - dependencies: 3942 - define-data-property "^1.1.4" 3943 - es-errors "^1.3.0" 3944 - function-bind "^1.1.2" 3945 - get-intrinsic "^1.2.4" 3946 - gopd "^1.0.1" 3947 - has-property-descriptors "^1.0.2" 3948 - 3949 - set-function-name@^2.0.1, set-function-name@^2.0.2: 3950 - version "2.0.2" 3951 - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" 3952 - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== 3953 - dependencies: 3954 - define-data-property "^1.1.4" 3955 - es-errors "^1.3.0" 3956 - functions-have-names "^1.2.3" 3957 - has-property-descriptors "^1.0.2" 3958 - 3959 shebang-command@^2.0.0: 3960 version "2.0.0" 3961 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" ··· 3968 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 3969 integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 3970 3971 - side-channel@^1.0.4, side-channel@^1.0.6: 3972 - version "1.0.6" 3973 - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" 3974 - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== 3975 - dependencies: 3976 - call-bind "^1.0.7" 3977 - es-errors "^1.3.0" 3978 - get-intrinsic "^1.2.4" 3979 - object-inspect "^1.13.1" 3980 - 3981 signal-exit@^3.0.3: 3982 version "3.0.7" 3983 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" ··· 3995 dependencies: 3996 kolorist "^1.6.0" 3997 3998 - slash@^3.0.0: 3999 - version "3.0.0" 4000 - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 4001 - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 4002 - 4003 source-map-js@^1.2.0: 4004 version "1.2.0" 4005 resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" ··· 4060 emoji-regex "^9.2.2" 4061 strip-ansi "^7.0.1" 4062 4063 - string.prototype.matchall@^4.0.10: 4064 - version "4.0.11" 4065 - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" 4066 - integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== 4067 - dependencies: 4068 - call-bind "^1.0.7" 4069 - define-properties "^1.2.1" 4070 - es-abstract "^1.23.2" 4071 - es-errors "^1.3.0" 4072 - es-object-atoms "^1.0.0" 4073 - get-intrinsic "^1.2.4" 4074 - gopd "^1.0.1" 4075 - has-symbols "^1.0.3" 4076 - internal-slot "^1.0.7" 4077 - regexp.prototype.flags "^1.5.2" 4078 - set-function-name "^2.0.2" 4079 - side-channel "^1.0.6" 4080 - 4081 - string.prototype.trim@^1.2.9: 4082 - version "1.2.9" 4083 - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" 4084 - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== 4085 - dependencies: 4086 - call-bind "^1.0.7" 4087 - define-properties "^1.2.1" 4088 - es-abstract "^1.23.0" 4089 - es-object-atoms "^1.0.0" 4090 - 4091 - string.prototype.trimend@^1.0.8: 4092 - version "1.0.8" 4093 - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" 4094 - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== 4095 - dependencies: 4096 - call-bind "^1.0.7" 4097 - define-properties "^1.2.1" 4098 - es-object-atoms "^1.0.0" 4099 - 4100 - string.prototype.trimstart@^1.0.8: 4101 - version "1.0.8" 4102 - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" 4103 - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== 4104 - dependencies: 4105 - call-bind "^1.0.7" 4106 - define-properties "^1.2.1" 4107 - es-object-atoms "^1.0.0" 4108 - 4109 "strip-ansi-cjs@npm:strip-ansi@^6.0.1": 4110 version "6.0.1" 4111 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" ··· 4210 commander "^2.20.0" 4211 source-map-support "~0.5.20" 4212 4213 - text-table@^0.2.0: 4214 - version "0.2.0" 4215 - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 4216 - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== 4217 - 4218 thenify-all@^1.0.0: 4219 version "1.6.0" 4220 resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" ··· 4237 fdir "^6.4.4" 4238 picomatch "^4.0.2" 4239 4240 tlds@^1.234.0: 4241 version "1.252.0" 4242 resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.252.0.tgz#71d9617f4ef4cc7347843bee72428e71b8b0f419" ··· 4249 dependencies: 4250 is-number "^7.0.0" 4251 4252 ts-interface-checker@^0.1.9: 4253 version "0.1.13" 4254 resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" ··· 4259 resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" 4260 integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== 4261 4262 - tslib@^1.8.1: 4263 - version "1.14.1" 4264 - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 4265 - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 4266 - 4267 - tsutils@^3.21.0: 4268 - version "3.21.0" 4269 - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" 4270 - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== 4271 - dependencies: 4272 - tslib "^1.8.1" 4273 - 4274 type-check@^0.4.0, type-check@~0.4.0: 4275 version "0.4.0" 4276 resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" ··· 4278 dependencies: 4279 prelude-ls "^1.2.1" 4280 4281 - type-fest@^0.20.2: 4282 - version "0.20.2" 4283 - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" 4284 - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 4285 - 4286 type-fest@^2.13.0: 4287 version "2.19.0" 4288 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" 4289 integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== 4290 4291 - typed-array-buffer@^1.0.2: 4292 - version "1.0.2" 4293 - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" 4294 - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== 4295 - dependencies: 4296 - call-bind "^1.0.7" 4297 - es-errors "^1.3.0" 4298 - is-typed-array "^1.1.13" 4299 - 4300 - typed-array-byte-length@^1.0.1: 4301 - version "1.0.1" 4302 - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" 4303 - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== 4304 - dependencies: 4305 - call-bind "^1.0.7" 4306 - for-each "^0.3.3" 4307 - gopd "^1.0.1" 4308 - has-proto "^1.0.3" 4309 - is-typed-array "^1.1.13" 4310 - 4311 - typed-array-byte-offset@^1.0.2: 4312 - version "1.0.2" 4313 - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" 4314 - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== 4315 - dependencies: 4316 - available-typed-arrays "^1.0.7" 4317 - call-bind "^1.0.7" 4318 - for-each "^0.3.3" 4319 - gopd "^1.0.1" 4320 - has-proto "^1.0.3" 4321 - is-typed-array "^1.1.13" 4322 - 4323 - typed-array-length@^1.0.6: 4324 - version "1.0.6" 4325 - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" 4326 - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== 4327 dependencies: 4328 - call-bind "^1.0.7" 4329 - for-each "^0.3.3" 4330 - gopd "^1.0.1" 4331 - has-proto "^1.0.3" 4332 - is-typed-array "^1.1.13" 4333 - possible-typed-array-names "^1.0.0" 4334 4335 typescript@^5.8.3: 4336 version "5.8.3" ··· 4343 integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== 4344 dependencies: 4345 multiformats "^9.4.2" 4346 - 4347 - unbox-primitive@^1.0.2: 4348 - version "1.0.2" 4349 - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" 4350 - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== 4351 - dependencies: 4352 - call-bind "^1.0.2" 4353 - has-bigints "^1.0.2" 4354 - has-symbols "^1.0.3" 4355 - which-boxed-primitive "^1.0.2" 4356 4357 unicode-canonical-property-names-ecmascript@^2.0.0: 4358 version "2.0.1" ··· 4377 resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" 4378 integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== 4379 4380 - update-browserslist-db@^1.0.13, update-browserslist-db@^1.1.3: 4381 version "1.1.3" 4382 resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" 4383 integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== ··· 4445 optionalDependencies: 4446 fsevents "~2.3.3" 4447 4448 - which-boxed-primitive@^1.0.2: 4449 - version "1.0.2" 4450 - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" 4451 - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== 4452 - dependencies: 4453 - is-bigint "^1.0.1" 4454 - is-boolean-object "^1.1.0" 4455 - is-number-object "^1.0.4" 4456 - is-string "^1.0.5" 4457 - is-symbol "^1.0.3" 4458 - 4459 - which-builtin-type@^1.1.3: 4460 - version "1.1.3" 4461 - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" 4462 - integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== 4463 - dependencies: 4464 - function.prototype.name "^1.1.5" 4465 - has-tostringtag "^1.0.0" 4466 - is-async-function "^2.0.0" 4467 - is-date-object "^1.0.5" 4468 - is-finalizationregistry "^1.0.2" 4469 - is-generator-function "^1.0.10" 4470 - is-regex "^1.1.4" 4471 - is-weakref "^1.0.2" 4472 - isarray "^2.0.5" 4473 - which-boxed-primitive "^1.0.2" 4474 - which-collection "^1.0.1" 4475 - which-typed-array "^1.1.9" 4476 - 4477 - which-collection@^1.0.1: 4478 - version "1.0.2" 4479 - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" 4480 - integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== 4481 - dependencies: 4482 - is-map "^2.0.3" 4483 - is-set "^2.0.3" 4484 - is-weakmap "^2.0.2" 4485 - is-weakset "^2.0.3" 4486 - 4487 - which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: 4488 - version "1.1.15" 4489 - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" 4490 - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== 4491 - dependencies: 4492 - available-typed-arrays "^1.0.7" 4493 - call-bind "^1.0.7" 4494 - for-each "^0.3.3" 4495 - gopd "^1.0.1" 4496 - has-tostringtag "^1.0.2" 4497 - 4498 which@^2.0.1: 4499 version "2.0.2" 4500 resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" ··· 4527 string-width "^5.0.1" 4528 strip-ansi "^7.0.1" 4529 4530 - wrappy@1: 4531 - version "1.0.2" 4532 - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 4533 - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 4534 - 4535 yallist@^3.0.2: 4536 version "3.1.1" 4537 resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" 4538 integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== 4539 - 4540 - yallist@^4.0.0: 4541 - version "4.0.0" 4542 - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 4543 - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 4544 4545 yaml@^2.3.4: 4546 version "2.4.1"
··· 68 "@atproto/lexicon" "^0.4.12" 69 zod "^3.23.8" 70 71 + "@babel/code-frame@^7.27.1": 72 version "7.27.1" 73 resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" 74 integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== ··· 82 resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" 83 integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== 84 85 "@babel/core@^7.22.1", "@babel/core@^7.27.4": 86 version "7.28.0" 87 resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" ··· 103 json5 "^2.2.3" 104 semver "^6.3.1" 105 106 + "@babel/generator@^7.28.0": 107 version "7.28.0" 108 resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" 109 integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== ··· 121 dependencies: 122 "@babel/types" "^7.27.3" 123 124 + "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": 125 version "7.27.2" 126 resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" 127 integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== ··· 186 "@babel/traverse" "^7.27.1" 187 "@babel/types" "^7.27.1" 188 189 + "@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.27.3": 190 version "7.27.3" 191 resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02" 192 integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== ··· 202 dependencies: 203 "@babel/types" "^7.27.1" 204 205 + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1": 206 version "7.27.1" 207 resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" 208 integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== 209 210 "@babel/helper-remap-async-to-generator@^7.27.1": 211 version "7.27.1" 212 resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" ··· 257 "@babel/traverse" "^7.27.1" 258 "@babel/types" "^7.27.1" 259 260 + "@babel/helpers@^7.27.6": 261 version "7.27.6" 262 resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c" 263 integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== ··· 265 "@babel/template" "^7.27.2" 266 "@babel/types" "^7.27.6" 267 268 + "@babel/parser@^7.27.2", "@babel/parser@^7.28.0": 269 version "7.28.0" 270 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" 271 integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== ··· 316 resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" 317 integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== 318 319 "@babel/plugin-syntax-import-assertions@^7.27.1": 320 version "7.27.1" 321 resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" ··· 329 integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== 330 dependencies: 331 "@babel/helper-plugin-utils" "^7.27.1" 332 333 "@babel/plugin-syntax-jsx@^7.27.1": 334 version "7.27.1" ··· 845 "@babel/types" "^7.4.4" 846 esutils "^2.0.2" 847 848 + "@babel/template@^7.27.1", "@babel/template@^7.27.2": 849 version "7.27.2" 850 resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" 851 integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== ··· 854 "@babel/parser" "^7.27.2" 855 "@babel/types" "^7.27.1" 856 857 + "@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": 858 version "7.28.0" 859 resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" 860 integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== ··· 867 "@babel/types" "^7.28.0" 868 debug "^4.3.1" 869 870 + "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.27.6", "@babel/types@^7.28.0", "@babel/types@^7.4.4": 871 version "7.28.1" 872 resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.1.tgz#2aaf3c10b31ba03a77ac84f52b3912a0edef4cf9" 873 integrity sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ== ··· 1005 resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz#4276edd5c105bc28b11c6a1f76fb9d29d1bd25c1" 1006 integrity sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA== 1007 1008 + "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": 1009 + version "4.9.1" 1010 + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" 1011 + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== 1012 dependencies: 1013 + eslint-visitor-keys "^3.4.3" 1014 1015 + "@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2": 1016 + version "4.12.2" 1017 + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" 1018 + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== 1019 1020 + "@eslint/config-array@^0.21.1": 1021 + version "0.21.1" 1022 + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" 1023 + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== 1024 + dependencies: 1025 + "@eslint/object-schema" "^2.1.7" 1026 + debug "^4.3.1" 1027 + minimatch "^3.1.2" 1028 + 1029 + "@eslint/config-helpers@^0.4.2": 1030 + version "0.4.2" 1031 + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" 1032 + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== 1033 + dependencies: 1034 + "@eslint/core" "^0.17.0" 1035 + 1036 + "@eslint/core@^0.17.0": 1037 + version "0.17.0" 1038 + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" 1039 + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== 1040 + dependencies: 1041 + "@types/json-schema" "^7.0.15" 1042 + 1043 + "@eslint/eslintrc@^3.3.1": 1044 + version "3.3.3" 1045 + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" 1046 + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== 1047 dependencies: 1048 ajv "^6.12.4" 1049 debug "^4.3.2" 1050 + espree "^10.0.1" 1051 + globals "^14.0.0" 1052 ignore "^5.2.0" 1053 import-fresh "^3.2.1" 1054 + js-yaml "^4.1.1" 1055 minimatch "^3.1.2" 1056 strip-json-comments "^3.1.1" 1057 1058 + "@eslint/js@9.39.2", "@eslint/js@^9.18.0": 1059 + version "9.39.2" 1060 + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599" 1061 + integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== 1062 + 1063 + "@eslint/object-schema@^2.1.7": 1064 + version "2.1.7" 1065 + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" 1066 + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== 1067 + 1068 + "@eslint/plugin-kit@^0.4.1": 1069 + version "0.4.1" 1070 + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" 1071 + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== 1072 + dependencies: 1073 + "@eslint/core" "^0.17.0" 1074 + levn "^0.4.1" 1075 + 1076 + "@humanfs/core@^0.19.1": 1077 + version "0.19.1" 1078 + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" 1079 + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== 1080 1081 + "@humanfs/node@^0.16.6": 1082 + version "0.16.7" 1083 + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" 1084 + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== 1085 dependencies: 1086 + "@humanfs/core" "^0.19.1" 1087 + "@humanwhocodes/retry" "^0.4.0" 1088 1089 "@humanwhocodes/module-importer@^1.0.1": 1090 version "1.0.1" 1091 resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" 1092 integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== 1093 1094 + "@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": 1095 + version "0.4.3" 1096 + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" 1097 + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== 1098 1099 "@isaacs/cliui@^8.0.2": 1100 version "8.0.2" ··· 1156 "@jridgewell/resolve-uri" "^3.1.0" 1157 "@jridgewell/sourcemap-codec" "^1.4.14" 1158 1159 "@nodelib/fs.scandir@2.1.5": 1160 version "2.1.5" 1161 resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" ··· 1169 resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 1170 integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 1171 1172 + "@nodelib/fs.walk@^1.2.3": 1173 version "1.2.8" 1174 resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 1175 integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== ··· 1330 resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.0.tgz#0a7eecae41f463d6591c8fecd7a5c5087345ee36" 1331 integrity sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA== 1332 1333 + "@types/estree@1.0.8", "@types/estree@^1.0.6": 1334 version "1.0.8" 1335 resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" 1336 integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== 1337 1338 + "@types/json-schema@^7.0.15": 1339 version "7.0.15" 1340 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" 1341 integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== 1342 1343 + "@typescript-eslint/eslint-plugin@8.52.0": 1344 + version "8.52.0" 1345 + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz#9a9f1d2ee974ed77a8b1bda94e77123f697ee8b4" 1346 + integrity sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q== 1347 + dependencies: 1348 + "@eslint-community/regexpp" "^4.12.2" 1349 + "@typescript-eslint/scope-manager" "8.52.0" 1350 + "@typescript-eslint/type-utils" "8.52.0" 1351 + "@typescript-eslint/utils" "8.52.0" 1352 + "@typescript-eslint/visitor-keys" "8.52.0" 1353 + ignore "^7.0.5" 1354 + natural-compare "^1.4.0" 1355 + ts-api-utils "^2.4.0" 1356 1357 + "@typescript-eslint/parser@8.52.0": 1358 + version "8.52.0" 1359 + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.52.0.tgz#9fae9f5f13ebb1c8f31a50c34381bfd6bf96a05f" 1360 + integrity sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg== 1361 dependencies: 1362 + "@typescript-eslint/scope-manager" "8.52.0" 1363 + "@typescript-eslint/types" "8.52.0" 1364 + "@typescript-eslint/typescript-estree" "8.52.0" 1365 + "@typescript-eslint/visitor-keys" "8.52.0" 1366 + debug "^4.4.3" 1367 1368 + "@typescript-eslint/project-service@8.52.0": 1369 + version "8.52.0" 1370 + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.52.0.tgz#5fb4c16af4eda6d74c70cbc62f5d3f77b96e4cbe" 1371 + integrity sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw== 1372 dependencies: 1373 + "@typescript-eslint/tsconfig-utils" "^8.52.0" 1374 + "@typescript-eslint/types" "^8.52.0" 1375 + debug "^4.4.3" 1376 1377 + "@typescript-eslint/scope-manager@8.52.0": 1378 + version "8.52.0" 1379 + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz#9884ff690fad30380ccabfb08af1ac200af6b4e5" 1380 + integrity sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA== 1381 + dependencies: 1382 + "@typescript-eslint/types" "8.52.0" 1383 + "@typescript-eslint/visitor-keys" "8.52.0" 1384 1385 + "@typescript-eslint/tsconfig-utils@8.52.0", "@typescript-eslint/tsconfig-utils@^8.52.0": 1386 + version "8.52.0" 1387 + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz#0296751c22ed05c83787a6eaec65ae221bd8b8ed" 1388 + integrity sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg== 1389 + 1390 + "@typescript-eslint/type-utils@8.52.0": 1391 + version "8.52.0" 1392 + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz#6e554113f8a074cf9b2faa818d2ebfccb867d6c5" 1393 + integrity sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ== 1394 dependencies: 1395 + "@typescript-eslint/types" "8.52.0" 1396 + "@typescript-eslint/typescript-estree" "8.52.0" 1397 + "@typescript-eslint/utils" "8.52.0" 1398 + debug "^4.4.3" 1399 + ts-api-utils "^2.4.0" 1400 1401 + "@typescript-eslint/types@8.52.0", "@typescript-eslint/types@^8.52.0": 1402 + version "8.52.0" 1403 + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.52.0.tgz#1eb0a16b324824bc23b89d109a267c38c9213c4a" 1404 + integrity sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg== 1405 + 1406 + "@typescript-eslint/typescript-estree@8.52.0": 1407 + version "8.52.0" 1408 + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz#2ad7721c671be2127951286cb7f44c4ce55b0591" 1409 + integrity sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ== 1410 dependencies: 1411 + "@typescript-eslint/project-service" "8.52.0" 1412 + "@typescript-eslint/tsconfig-utils" "8.52.0" 1413 + "@typescript-eslint/types" "8.52.0" 1414 + "@typescript-eslint/visitor-keys" "8.52.0" 1415 + debug "^4.4.3" 1416 + minimatch "^9.0.5" 1417 + semver "^7.7.3" 1418 + tinyglobby "^0.2.15" 1419 + ts-api-utils "^2.4.0" 1420 1421 + "@typescript-eslint/utils@8.52.0": 1422 + version "8.52.0" 1423 + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.52.0.tgz#b249be8264899b80d996fa353b4b84da4662f962" 1424 + integrity sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ== 1425 dependencies: 1426 + "@eslint-community/eslint-utils" "^4.9.1" 1427 + "@typescript-eslint/scope-manager" "8.52.0" 1428 + "@typescript-eslint/types" "8.52.0" 1429 + "@typescript-eslint/typescript-estree" "8.52.0" 1430 1431 + "@typescript-eslint/visitor-keys@8.52.0": 1432 + version "8.52.0" 1433 + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz#50361c48a6302676230fe498f80f6decce4bf673" 1434 + integrity sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ== 1435 + dependencies: 1436 + "@typescript-eslint/types" "8.52.0" 1437 + eslint-visitor-keys "^4.2.1" 1438 1439 "@vitejs/plugin-legacy@^7.0.0": 1440 version "7.0.0" ··· 1460 resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" 1461 integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== 1462 1463 + acorn@^8.14.0, acorn@^8.15.0: 1464 version "8.15.0" 1465 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" 1466 integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== 1467 1468 ajv@8.12.0: 1469 version "8.12.0" ··· 1542 resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 1543 integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 1544 1545 autoprefixer@^10.4.19: 1546 version "10.4.21" 1547 resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.21.tgz#77189468e7a8ad1d9a37fbc08efc9f480cf0a95d" ··· 1553 normalize-range "^0.1.2" 1554 picocolors "^1.1.1" 1555 postcss-value-parser "^4.2.0" 1556 1557 await-lock@^2.2.2: 1558 version "2.2.2" ··· 1646 dependencies: 1647 meow "^13.0.0" 1648 1649 browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.0, browserslist@^4.25.1: 1650 version "4.25.1" 1651 resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.1.tgz#ba9e8e6f298a1d86f829c9b975e07948967bb111" ··· 1671 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 1672 integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 1673 1674 callsites@^3.0.0: 1675 version "3.1.0" 1676 resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" ··· 1686 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" 1687 integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== 1688 1689 + caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726: 1690 version "1.0.30001727" 1691 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" 1692 integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== ··· 1814 resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.44.0.tgz#db4fd4fa07933c1d6898c8b112a1119a9336e959" 1815 integrity sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw== 1816 1817 + cross-spawn@^7.0.0: 1818 version "7.0.3" 1819 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 1820 integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== ··· 1823 shebang-command "^2.0.0" 1824 which "^2.0.1" 1825 1826 + cross-spawn@^7.0.3, cross-spawn@^7.0.6: 1827 version "7.0.6" 1828 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" 1829 integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== ··· 1853 resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" 1854 integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== 1855 1856 debug@2.6.9: 1857 version "2.6.9" 1858 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" ··· 1874 dependencies: 1875 ms "2.1.2" 1876 1877 + debug@^4.4.3: 1878 + version "4.4.3" 1879 + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" 1880 + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== 1881 + dependencies: 1882 + ms "^2.1.3" 1883 + 1884 deep-extend@^0.6.0: 1885 version "0.6.0" 1886 resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" ··· 1891 resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" 1892 integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== 1893 1894 didyoumean@^1.2.2: 1895 version "1.2.2" 1896 resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" 1897 integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== 1898 1899 dlv@^1.1.3: 1900 version "1.1.3" 1901 resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" 1902 integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== 1903 1904 dom-serializer@^2.0.0: 1905 version "2.0.0" 1906 resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" ··· 1936 resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" 1937 integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== 1938 1939 + electron-to-chromium@^1.5.173: 1940 version "1.5.182" 1941 resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz#4ab73104f893938acb3ab9c28d7bec170c116b3e" 1942 integrity sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA== ··· 1956 resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" 1957 integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== 1958 1959 esbuild@^0.25.0: 1960 version "0.25.6" 1961 resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.6.tgz#9b82a3db2fa131aec069ab040fd57ed0a880cdcd" ··· 1998 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 1999 integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 2000 2001 + eslint-plugin-simple-import-sort@^12.1.1: 2002 + version "12.1.1" 2003 + resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz#e64bfdaf91c5b98a298619aa634a9f7aa43b709e" 2004 + integrity sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA== 2005 2006 + eslint-scope@^8.4.0: 2007 + version "8.4.0" 2008 + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" 2009 + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== 2010 dependencies: 2011 esrecurse "^4.3.0" 2012 estraverse "^5.2.0" 2013 2014 + eslint-visitor-keys@^3.4.3: 2015 version "3.4.3" 2016 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" 2017 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== 2018 2019 + eslint-visitor-keys@^4.2.1: 2020 + version "4.2.1" 2021 + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" 2022 + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== 2023 + 2024 + eslint@^9.18.0: 2025 + version "9.39.2" 2026 + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c" 2027 + integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== 2028 dependencies: 2029 + "@eslint-community/eslint-utils" "^4.8.0" 2030 + "@eslint-community/regexpp" "^4.12.1" 2031 + "@eslint/config-array" "^0.21.1" 2032 + "@eslint/config-helpers" "^0.4.2" 2033 + "@eslint/core" "^0.17.0" 2034 + "@eslint/eslintrc" "^3.3.1" 2035 + "@eslint/js" "9.39.2" 2036 + "@eslint/plugin-kit" "^0.4.1" 2037 + "@humanfs/node" "^0.16.6" 2038 "@humanwhocodes/module-importer" "^1.0.1" 2039 + "@humanwhocodes/retry" "^0.4.2" 2040 + "@types/estree" "^1.0.6" 2041 ajv "^6.12.4" 2042 chalk "^4.0.0" 2043 + cross-spawn "^7.0.6" 2044 debug "^4.3.2" 2045 escape-string-regexp "^4.0.0" 2046 + eslint-scope "^8.4.0" 2047 + eslint-visitor-keys "^4.2.1" 2048 + espree "^10.4.0" 2049 + esquery "^1.5.0" 2050 esutils "^2.0.2" 2051 fast-deep-equal "^3.1.3" 2052 + file-entry-cache "^8.0.0" 2053 find-up "^5.0.0" 2054 glob-parent "^6.0.2" 2055 ignore "^5.2.0" 2056 imurmurhash "^0.1.4" 2057 is-glob "^4.0.0" 2058 json-stable-stringify-without-jsonify "^1.0.1" 2059 lodash.merge "^4.6.2" 2060 minimatch "^3.1.2" 2061 natural-compare "^1.4.0" 2062 optionator "^0.9.3" 2063 2064 + espree@^10.0.1, espree@^10.4.0: 2065 + version "10.4.0" 2066 + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" 2067 + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== 2068 dependencies: 2069 + acorn "^8.15.0" 2070 acorn-jsx "^5.3.2" 2071 + eslint-visitor-keys "^4.2.1" 2072 2073 + esquery@^1.5.0: 2074 + version "1.7.0" 2075 + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" 2076 + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== 2077 dependencies: 2078 estraverse "^5.1.0" 2079 ··· 2084 dependencies: 2085 estraverse "^5.2.0" 2086 2087 + estraverse@^5.1.0, estraverse@^5.2.0: 2088 version "5.3.0" 2089 resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" 2090 integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== ··· 2119 resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 2120 integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 2121 2122 + fast-glob@^3.3.0: 2123 version "3.3.2" 2124 resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" 2125 integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== ··· 2152 resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.6.tgz#2b268c0232697063111bbf3f64810a2a741ba281" 2153 integrity sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== 2154 2155 + fdir@^6.5.0: 2156 + version "6.5.0" 2157 + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" 2158 + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== 2159 + 2160 + file-entry-cache@^8.0.0: 2161 + version "8.0.0" 2162 + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" 2163 + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== 2164 dependencies: 2165 + flat-cache "^4.0.0" 2166 2167 fill-range@^7.0.1: 2168 version "7.0.1" ··· 2179 locate-path "^6.0.0" 2180 path-exists "^4.0.0" 2181 2182 + flat-cache@^4.0.0: 2183 + version "4.0.1" 2184 + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" 2185 + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== 2186 dependencies: 2187 flatted "^3.2.9" 2188 + keyv "^4.5.4" 2189 2190 flatted@^3.2.9: 2191 version "3.3.1" 2192 resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" 2193 integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== 2194 2195 foreground-child@^3.1.0: 2196 version "3.1.1" 2197 resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" ··· 2205 resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" 2206 integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== 2207 2208 fsevents@~2.3.2, fsevents@~2.3.3: 2209 version "2.3.3" 2210 resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" ··· 2215 resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" 2216 integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 2217 2218 gensync@^1.0.0-beta.2: 2219 version "1.0.0-beta.2" 2220 resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 2221 integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== 2222 2223 get-stream@^6.0.0: 2224 version "6.0.1" 2225 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" 2226 integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== 2227 2228 glob-parent@^5.1.2, glob-parent@~5.1.2: 2229 version "5.1.2" ··· 2250 minipass "^7.0.4" 2251 path-scurry "^1.10.2" 2252 2253 + globals@^14.0.0: 2254 + version "14.0.0" 2255 + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" 2256 + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== 2257 2258 + globals@^15.14.0: 2259 + version "15.15.0" 2260 + resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" 2261 + integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== 2262 2263 globrex@^0.1.2: 2264 version "0.1.2" 2265 resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" 2266 integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== 2267 2268 graphemer@^1.4.0: 2269 version "1.4.0" 2270 resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" 2271 integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== 2272 2273 has-flag@^4.0.0: 2274 version "4.0.0" 2275 resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 2276 integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 2277 2278 + hasown@^2.0.0, hasown@^2.0.2: 2279 version "2.0.2" 2280 resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" 2281 integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== ··· 2297 resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" 2298 integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== 2299 2300 + ignore@^7.0.5: 2301 + version "7.0.5" 2302 + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" 2303 + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== 2304 + 2305 import-fresh@^3.2.1: 2306 version "3.3.0" 2307 resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" ··· 2315 resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 2316 integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== 2317 2318 ini@~1.3.0: 2319 version "1.3.8" 2320 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" 2321 integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== 2322 2323 is-binary-path@~2.1.0: 2324 version "2.1.0" 2325 resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" ··· 2327 dependencies: 2328 binary-extensions "^2.0.0" 2329 2330 is-core-module@^2.13.0: 2331 version "2.13.1" 2332 resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" ··· 2341 dependencies: 2342 hasown "^2.0.2" 2343 2344 is-docker@^2.0.0: 2345 version "2.2.1" 2346 resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" ··· 2351 resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 2352 integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 2353 2354 is-fullwidth-code-point@^3.0.0: 2355 version "3.0.0" 2356 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 2357 integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 2358 2359 is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: 2360 version "4.0.3" 2361 resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" ··· 2363 dependencies: 2364 is-extglob "^2.1.1" 2365 2366 is-number@^7.0.0: 2367 version "7.0.0" 2368 resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 2369 integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 2370 2371 is-port-reachable@4.0.0: 2372 version "4.0.0" 2373 resolved "https://registry.yarnpkg.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d" 2374 integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig== 2375 2376 is-stream@^2.0.0: 2377 version "2.0.1" 2378 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" 2379 integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== 2380 2381 is-wsl@^2.2.0: 2382 version "2.2.0" 2383 resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" ··· 2385 dependencies: 2386 is-docker "^2.0.0" 2387 2388 isexe@^2.0.0: 2389 version "2.0.0" 2390 resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" ··· 2395 resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" 2396 integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== 2397 2398 jackspeak@^2.3.6: 2399 version "2.3.6" 2400 resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" ··· 2409 resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" 2410 integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== 2411 2412 + js-tokens@^4.0.0: 2413 version "4.0.0" 2414 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 2415 integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 2416 2417 + js-yaml@^4.1.1: 2418 + version "4.1.1" 2419 + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" 2420 + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== 2421 dependencies: 2422 argparse "^2.0.1" 2423 ··· 2456 resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" 2457 integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== 2458 2459 + keyv@^4.5.4: 2460 version "4.5.4" 2461 resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" 2462 integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== ··· 2503 resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" 2504 integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== 2505 2506 lodash.merge@^4.6.2: 2507 version "4.6.2" 2508 resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" 2509 integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 2510 2511 lru-cache@^10.2.0: 2512 version "10.2.0" 2513 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" ··· 2520 dependencies: 2521 yallist "^3.0.2" 2522 2523 "magic-string@0.x >= 0.26.0", magic-string@^0.30.17: 2524 version "0.30.17" 2525 resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" ··· 2537 resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" 2538 integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== 2539 2540 + merge2@^1.3.0: 2541 version "1.4.1" 2542 resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 2543 integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== ··· 2572 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 2573 integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== 2574 2575 + minimatch@3.1.2, minimatch@^3.1.2: 2576 version "3.1.2" 2577 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 2578 integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== ··· 2586 dependencies: 2587 brace-expansion "^2.0.1" 2588 2589 + minimatch@^9.0.5: 2590 + version "9.0.5" 2591 + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" 2592 + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== 2593 + dependencies: 2594 + brace-expansion "^2.0.1" 2595 + 2596 minimist@^1.2.0: 2597 version "1.2.8" 2598 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" ··· 2660 css-select "^5.1.0" 2661 he "1.2.0" 2662 2663 + node-releases@^2.0.19: 2664 version "2.0.19" 2665 resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" 2666 integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== ··· 2689 dependencies: 2690 boolbase "^1.0.0" 2691 2692 + object-assign@^4.0.1: 2693 version "4.1.1" 2694 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 2695 integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== ··· 2699 resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" 2700 integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== 2701 2702 on-headers@~1.1.0: 2703 version "1.1.0" 2704 resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" 2705 integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== 2706 2707 onetime@^5.1.2: 2708 version "5.1.2" 2709 resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" ··· 2749 resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 2750 integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 2751 2752 path-is-inside@1.0.2: 2753 version "1.0.2" 2754 resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" ··· 2777 resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" 2778 integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== 2779 2780 picocolors@^1.0.0, picocolors@^1.1.1: 2781 version "1.1.1" 2782 resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" ··· 2792 resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" 2793 integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== 2794 2795 + picomatch@^4.0.3: 2796 + version "4.0.3" 2797 + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" 2798 + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== 2799 + 2800 pify@^2.3.0: 2801 version "2.3.0" 2802 resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" ··· 2806 version "4.0.6" 2807 resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" 2808 integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== 2809 2810 postcss-import@^15.1.0: 2811 version "15.1.0" ··· 2879 resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" 2880 integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 2881 2882 punycode@^2.1.0: 2883 version "2.3.1" 2884 resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" ··· 2904 minimist "^1.2.0" 2905 strip-json-comments "~2.0.1" 2906 2907 read-cache@^1.0.0: 2908 version "1.0.0" 2909 resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" ··· 2918 dependencies: 2919 picomatch "^2.2.1" 2920 2921 regenerate-unicode-properties@^10.2.0: 2922 version "10.2.0" 2923 resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" ··· 2934 version "0.14.1" 2935 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" 2936 integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== 2937 2938 regexpu-core@^6.2.0: 2939 version "6.2.0" ··· 3002 path-parse "^1.0.7" 3003 supports-preserve-symlinks-flag "^1.0.0" 3004 3005 reusify@^1.0.4: 3006 version "1.0.4" 3007 resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 3008 integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 3009 3010 rollup@^4.40.0: 3011 version "4.45.0" ··· 3043 dependencies: 3044 queue-microtask "^1.2.2" 3045 3046 safe-buffer@5.2.1, safe-buffer@^5.0.1: 3047 version "5.2.1" 3048 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 3049 integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 3050 3051 semver@^6.3.1: 3052 version "6.3.1" 3053 resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" 3054 integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== 3055 3056 + semver@^7.7.3: 3057 + version "7.7.3" 3058 + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" 3059 + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== 3060 3061 serve-handler@6.1.6: 3062 version "6.1.6" ··· 3088 serve-handler "6.1.6" 3089 update-check "1.5.4" 3090 3091 shebang-command@^2.0.0: 3092 version "2.0.0" 3093 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" ··· 3100 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 3101 integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 3102 3103 signal-exit@^3.0.3: 3104 version "3.0.7" 3105 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" ··· 3117 dependencies: 3118 kolorist "^1.6.0" 3119 3120 source-map-js@^1.2.0: 3121 version "1.2.0" 3122 resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" ··· 3177 emoji-regex "^9.2.2" 3178 strip-ansi "^7.0.1" 3179 3180 "strip-ansi-cjs@npm:strip-ansi@^6.0.1": 3181 version "6.0.1" 3182 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" ··· 3281 commander "^2.20.0" 3282 source-map-support "~0.5.20" 3283 3284 thenify-all@^1.0.0: 3285 version "1.6.0" 3286 resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" ··· 3303 fdir "^6.4.4" 3304 picomatch "^4.0.2" 3305 3306 + tinyglobby@^0.2.15: 3307 + version "0.2.15" 3308 + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" 3309 + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== 3310 + dependencies: 3311 + fdir "^6.5.0" 3312 + picomatch "^4.0.3" 3313 + 3314 tlds@^1.234.0: 3315 version "1.252.0" 3316 resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.252.0.tgz#71d9617f4ef4cc7347843bee72428e71b8b0f419" ··· 3323 dependencies: 3324 is-number "^7.0.0" 3325 3326 + ts-api-utils@^2.4.0: 3327 + version "2.4.0" 3328 + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" 3329 + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== 3330 + 3331 ts-interface-checker@^0.1.9: 3332 version "0.1.13" 3333 resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" ··· 3338 resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" 3339 integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== 3340 3341 type-check@^0.4.0, type-check@~0.4.0: 3342 version "0.4.0" 3343 resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" ··· 3345 dependencies: 3346 prelude-ls "^1.2.1" 3347 3348 type-fest@^2.13.0: 3349 version "2.19.0" 3350 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" 3351 integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== 3352 3353 + typescript-eslint@^8.20.0: 3354 + version "8.52.0" 3355 + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.52.0.tgz#b8c156b6f2b4dee202a85712ff6a37f614476413" 3356 + integrity sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA== 3357 dependencies: 3358 + "@typescript-eslint/eslint-plugin" "8.52.0" 3359 + "@typescript-eslint/parser" "8.52.0" 3360 + "@typescript-eslint/typescript-estree" "8.52.0" 3361 + "@typescript-eslint/utils" "8.52.0" 3362 3363 typescript@^5.8.3: 3364 version "5.8.3" ··· 3371 integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== 3372 dependencies: 3373 multiformats "^9.4.2" 3374 3375 unicode-canonical-property-names-ecmascript@^2.0.0: 3376 version "2.0.1" ··· 3395 resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" 3396 integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== 3397 3398 + update-browserslist-db@^1.1.3: 3399 version "1.1.3" 3400 resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" 3401 integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== ··· 3463 optionalDependencies: 3464 fsevents "~2.3.3" 3465 3466 which@^2.0.1: 3467 version "2.0.2" 3468 resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" ··· 3495 string-width "^5.0.1" 3496 strip-ansi "^7.0.1" 3497 3498 yallist@^3.0.2: 3499 version "3.1.1" 3500 resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" 3501 integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== 3502 3503 yaml@^2.3.4: 3504 version "2.4.1"
+271
eslint.config.mjs
···
··· 1 + // @ts-check 2 + import js from '@eslint/js' 3 + import tseslint from 'typescript-eslint' 4 + import { defineConfig } from 'eslint/config'; 5 + import react from 'eslint-plugin-react' 6 + import reactHooks from 'eslint-plugin-react-hooks' 7 + // @ts-expect-error no types 8 + import reactNative from 'eslint-plugin-react-native' 9 + // @ts-expect-error no types 10 + import reactNativeA11y from 'eslint-plugin-react-native-a11y' 11 + import simpleImportSort from 'eslint-plugin-simple-import-sort' 12 + import importX from 'eslint-plugin-import-x' 13 + import lingui from 'eslint-plugin-lingui' 14 + import reactCompiler from 'eslint-plugin-react-compiler' 15 + import bskyInternal from 'eslint-plugin-bsky-internal' 16 + import globals from 'globals' 17 + import tsParser from '@typescript-eslint/parser' 18 + 19 + export default defineConfig( 20 + /** 21 + * Global ignores 22 + */ 23 + { 24 + ignores: [ 25 + '**/__mocks__/*.ts', 26 + 'src/platform/polyfills.ts', 27 + 'src/third-party/**', 28 + 'ios/**', 29 + 'android/**', 30 + 'coverage/**', 31 + '*.lock', 32 + '.husky/**', 33 + 'patches/**', 34 + '*.html', 35 + 'bskyweb/**', 36 + 'bskyembed/**', 37 + 'src/locale/locales/_build/**', 38 + 'src/locale/locales/**/*.js', 39 + '*.e2e.ts', 40 + '*.e2e.tsx', 41 + 'eslint.config.mjs', 42 + ], 43 + }, 44 + 45 + /** 46 + * Base configurations 47 + */ 48 + js.configs.recommended, 49 + tseslint.configs.recommendedTypeChecked, 50 + reactHooks.configs.flat.recommended, 51 + // @ts-expect-error https://github.com/un-ts/eslint-plugin-import-x/issues/439 52 + importX.flatConfigs.recommended, 53 + importX.flatConfigs.typescript, 54 + importX.flatConfigs['react-native'], 55 + 56 + /** 57 + * Main configuration for all JS/TS/JSX/TSX files 58 + */ 59 + { 60 + files: ['**/*.{js,jsx,ts,tsx}'], 61 + plugins: { 62 + react, 63 + 'react-native': reactNative, 64 + 'react-native-a11y': reactNativeA11y, 65 + 'simple-import-sort': simpleImportSort, 66 + lingui, 67 + 'react-compiler': reactCompiler, 68 + 'bsky-internal': bskyInternal, 69 + }, 70 + languageOptions: { 71 + ecmaVersion: 'latest', 72 + sourceType: 'module', 73 + globals: { 74 + ...globals.browser, 75 + }, 76 + parserOptions: { 77 + parser: tsParser, 78 + projectService: true, 79 + ecmaFeatures: { 80 + jsx: true, 81 + }, 82 + }, 83 + }, 84 + settings: { 85 + react: { 86 + version: 'detect', 87 + }, 88 + componentWrapperFunctions: ['observer'], 89 + }, 90 + rules: { 91 + /** 92 + * Custom rules 93 + */ 94 + 'bsky-internal/avoid-unwrapped-text': [ 95 + 'error', 96 + { 97 + impliedTextComponents: [ 98 + 'H1', 99 + 'H2', 100 + 'H3', 101 + 'H4', 102 + 'H5', 103 + 'H6', 104 + 'P', 105 + 'Admonition', 106 + 'Admonition.Admonition', 107 + 'Toast.Action', 108 + 'AgeAssuranceAdmonition', 109 + 'Span', 110 + 'StackedButton', 111 + ], 112 + impliedTextProps: [], 113 + suggestedTextWrappers: { 114 + Button: 'ButtonText', 115 + 'ToggleButton.Button': 'ToggleButton.ButtonText', 116 + 'SegmentedControl.Item': 'SegmentedControl.ItemText', 117 + }, 118 + }, 119 + ], 120 + 'bsky-internal/use-exact-imports': 'error', 121 + 'bsky-internal/use-typed-gates': 'error', 122 + 'bsky-internal/use-prefixed-imports': 'error', 123 + 124 + /** 125 + * React & React Native 126 + */ 127 + ...react.configs.recommended.rules, 128 + ...react.configs['jsx-runtime'].rules, 129 + 'react/no-unescaped-entities': 'off', 130 + 'react/prop-types': 'off', 131 + 'react-native/no-inline-styles': 'off', 132 + ...reactNativeA11y.configs.all.rules, 133 + 'react-compiler/react-compiler': 'warn', 134 + // TODO: Fix these and set to error 135 + 'react-hooks/set-state-in-effect': 'warn', 136 + 'react-hooks/purity': 'warn', 137 + 'react-hooks/refs': 'warn', 138 + 'react-hooks/immutability': 'warn', 139 + 140 + /** 141 + * Import sorting 142 + */ 143 + 'simple-import-sort/imports': [ 144 + 'error', 145 + { 146 + groups: [ 147 + // Side effect imports. 148 + ['^\\u0000'], 149 + // Node.js builtins prefixed with `node:`. 150 + ['^node:'], 151 + // Packages. 152 + // Things that start with a letter (or digit or underscore), or `@` followed by a letter. 153 + // React/React Native prioritized, followed by expo 154 + // Followed by all packages excluding unprefixed relative ones 155 + [ 156 + '^(react\\/(.*)$)|^(react$)|^(react-native(.*)$)', 157 + '^(expo(.*)$)|^(expo$)', 158 + '^(?!(?:alf|components|lib|locale|logger|platform|screens|state|view)(?:$|\\/))@?\\w', 159 + ], 160 + // Relative imports. 161 + // Ideally, anything that starts with a dot or # 162 + // due to unprefixed relative imports being used, we whitelist the relative paths we use 163 + // (?:$|\\/) matches end of string or / 164 + [ 165 + '^(?:#\\/)?(?:lib|state|logger|platform|locale)(?:$|\\/)', 166 + '^(?:#\\/)?view(?:$|\\/)', 167 + '^(?:#\\/)?screens(?:$|\\/)', 168 + '^(?:#\\/)?alf(?:$|\\/)', 169 + '^(?:#\\/)?components(?:$|\\/)', 170 + '^#\\/', 171 + '^\\.', 172 + ], 173 + // anything else - hopefully we don't have any of these 174 + ['^'], 175 + ], 176 + }, 177 + ], 178 + 'simple-import-sort/exports': 'error', 179 + 180 + /** 181 + * Import linting 182 + */ 183 + 'import-x/consistent-type-specifier-style': ['warn', 'prefer-inline'], 184 + 'import-x/no-unresolved': ['error', { 185 + /* 186 + * The `postinstall` hook runs `compile-if-needed` locally, but not in 187 + * CI. For CI-sake, ignore this. 188 + */ 189 + ignore: ['^#\/locale\/locales\/.+\/messages'], 190 + }], 191 + 192 + /** 193 + * TypeScript-specific rules 194 + */ 195 + 'no-unused-vars': 'off', // off, we use TS-specific rule below 196 + '@typescript-eslint/no-unused-vars': [ 197 + 'error', 198 + { 199 + argsIgnorePattern: '^_', 200 + varsIgnorePattern: '^_.+', 201 + caughtErrors: 'none', 202 + ignoreRestSiblings: true, 203 + }, 204 + ], 205 + '@typescript-eslint/consistent-type-imports': [ 206 + 'warn', 207 + {prefer: 'type-imports', fixStyle: 'inline-type-imports'}, 208 + ], 209 + '@typescript-eslint/no-require-imports': 'off', 210 + '@typescript-eslint/no-unused-expressions': ['error', { 211 + allowTernary: true, 212 + }], 213 + /** 214 + * Maintain previous behavior - these are stricter in typescript-eslint 215 + * v8 `warn` ones are probably worth fixing. `off` ones are a bit too 216 + * nit-picky 217 + */ 218 + '@typescript-eslint/no-explicit-any': 'off', 219 + '@typescript-eslint/ban-ts-comment': 'off', 220 + '@typescript-eslint/no-empty-object-type': 'off', 221 + '@typescript-eslint/no-unsafe-function-type': 'off', 222 + '@typescript-eslint/no-unsafe-assignment': 'off', 223 + '@typescript-eslint/unbound-method': 'off', 224 + '@typescript-eslint/no-unsafe-argument': 'off', 225 + '@typescript-eslint/no-unsafe-return': 'off', 226 + '@typescript-eslint/no-unsafe-member-access': 'warn', 227 + '@typescript-eslint/no-unsafe-call': 'warn', 228 + '@typescript-eslint/no-floating-promises': 'warn', 229 + '@typescript-eslint/no-misused-promises': 'warn', 230 + '@typescript-eslint/require-await': 'warn', 231 + '@typescript-eslint/no-unsafe-enum-comparison': 'warn', 232 + '@typescript-eslint/no-unnecessary-type-assertion': 'warn', 233 + '@typescript-eslint/no-redundant-type-constituents': 'warn', 234 + '@typescript-eslint/no-duplicate-type-constituents': 'warn', 235 + '@typescript-eslint/no-base-to-string': 'warn', 236 + '@typescript-eslint/prefer-promise-reject-errors': 'warn', 237 + '@typescript-eslint/await-thenable': 'warn', 238 + 239 + /** 240 + * Turn off rules that we haven't enforced thus far 241 + */ 242 + 'no-empty-pattern': 'off', 243 + 'no-async-promise-executor': 'off', 244 + 'no-constant-binary-expression': 'warn', 245 + 'prefer-const': 'off', 246 + 'no-empty': 'off', 247 + 'no-unsafe-optional-chaining': 'off', 248 + 'no-prototype-builtins': 'off', 249 + 'no-var': 'off', 250 + 'prefer-rest-params': 'off', 251 + 'no-case-declarations': 'off', 252 + 'no-irregular-whitespace': 'off', 253 + 'no-useless-escape': 'off', 254 + 'no-sparse-arrays': 'off', 255 + 'no-fallthrough': 'off', 256 + 'no-control-regex': 'off', 257 + }, 258 + }, 259 + 260 + /** 261 + * Test files configuration 262 + */ 263 + { 264 + files: ['**/__tests__/**/*.{js,jsx,ts,tsx}', '**/*.test.{js,jsx,ts,tsx}'], 265 + languageOptions: { 266 + globals: { 267 + ...globals.jest, 268 + } 269 + }, 270 + }, 271 + )
+9 -15
eslint/__tests__/avoid-unwrapped-text.test.js
··· 1 const {RuleTester} = require('eslint') 2 const avoidUnwrappedText = require('../avoid-unwrapped-text') 3 4 const ruleTester = new RuleTester({ 5 - parser: require.resolve('@typescript-eslint/parser'), 6 - parserOptions: { 7 - ecmaFeatures: { 8 - jsx: true, 9 }, 10 - ecmaVersion: 6, 11 - sourceType: 'module', 12 }, 13 }) 14 ··· 770 <Trans>{foo}</Trans> 771 </View> 772 `, 773 - errors: 1, 774 - }, 775 - 776 - { 777 - code: ` 778 - <View> 779 - <Trans>{'foo'}</Trans> 780 - </View> 781 - `, 782 errors: 1, 783 }, 784
··· 1 const {RuleTester} = require('eslint') 2 + const tseslint = require('typescript-eslint') 3 const avoidUnwrappedText = require('../avoid-unwrapped-text') 4 5 const ruleTester = new RuleTester({ 6 + languageOptions: { 7 + parser: tseslint.parser, 8 + parserOptions: { 9 + ecmaFeatures: { 10 + jsx: true, 11 + }, 12 + ecmaVersion: 'latest', 13 + sourceType: 'module', 14 }, 15 }, 16 }) 17 ··· 773 <Trans>{foo}</Trans> 774 </View> 775 `, 776 errors: 1, 777 }, 778
+290 -262
eslint/avoid-unwrapped-text.js
··· 29 return reversedIdentifiers.reverse().join('.') 30 } 31 32 - exports.create = function create(context) { 33 - const options = context.options[0] || {} 34 - const impliedTextProps = options.impliedTextProps ?? [] 35 - const impliedTextComponents = options.impliedTextComponents ?? [] 36 - const suggestedTextWrappers = options.suggestedTextWrappers ?? {} 37 - const textProps = [...impliedTextProps] 38 - const textComponents = ['Text', ...impliedTextComponents] 39 40 - function isTextComponent(tagName) { 41 - return textComponents.includes(tagName) || tagName.endsWith('Text') 42 - } 43 44 - return { 45 - JSXText(node) { 46 - if (typeof node.value !== 'string' || hasOnlyLineBreak(node.value)) { 47 - return 48 - } 49 - let parent = node.parent 50 - while (parent) { 51 - if (parent.type === 'JSXElement') { 52 - const tagName = getTagName(parent) 53 - if (isTextComponent(tagName)) { 54 - // We're good. 55 return 56 } 57 - if (tagName === 'Trans') { 58 - // Exit and rely on the traversal for <Trans> JSXElement (code below). 59 - // TODO: Maybe validate that it's present. 60 return 61 } 62 - const suggestedWrapper = suggestedTextWrappers[tagName] 63 - let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 64 - if (tagName !== 'View' && !suggestedWrapper) { 65 - message += 66 - ' If <' + 67 - tagName + 68 - '> is guaranteed to render <Text>, ' + 69 - 'rename it to <' + 70 - tagName + 71 - 'Text> or add it to impliedTextComponents.' 72 - } 73 - context.report({ 74 - node, 75 - message, 76 - }) 77 return 78 } 79 80 - if ( 81 - parent.type === 'JSXAttribute' && 82 - parent.name.type === 'JSXIdentifier' && 83 - parent.parent.type === 'JSXOpeningElement' && 84 - parent.parent.parent.type === 'JSXElement' 85 - ) { 86 - const tagName = getTagName(parent.parent.parent) 87 - const propName = parent.name.name 88 if ( 89 - textProps.includes(tagName + ' ' + propName) || 90 - propName === 'text' || 91 - propName.endsWith('Text') 92 ) { 93 - // We're good. 94 - return 95 } 96 - const message = 97 - 'Wrap this string in <Text>.' + 98 - ' If `' + 99 - propName + 100 - '` is guaranteed to be wrapped in <Text>, ' + 101 - 'rename it to `' + 102 - propName + 103 - 'Text' + 104 - '` or add it to impliedTextProps.' 105 - context.report({ 106 - node, 107 - message, 108 - }) 109 return 110 } 111 - 112 - parent = parent.parent 113 - continue 114 - } 115 - }, 116 - Literal(node) { 117 - if (typeof node.value !== 'string' && typeof node.value !== 'number') { 118 - return 119 - } 120 - let parent = node.parent 121 - while (parent) { 122 - if (parent.type === 'JSXElement') { 123 - const tagName = getTagName(parent) 124 - if (isTextComponent(tagName)) { 125 - // We're good. 126 return 127 } 128 - if (tagName === 'Trans') { 129 - // Exit and rely on the traversal for <Trans> JSXElement (code below). 130 - // TODO: Maybe validate that it's present. 131 - return 132 } 133 - const suggestedWrapper = suggestedTextWrappers[tagName] 134 - let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 135 - if (tagName !== 'View' && !suggestedWrapper) { 136 - message += 137 - ' If <' + 138 - tagName + 139 - '> is guaranteed to render <Text>, ' + 140 - 'rename it to <' + 141 - tagName + 142 - 'Text> or add it to impliedTextComponents.' 143 } 144 - context.report({ 145 - node, 146 - message, 147 - }) 148 return 149 } 150 - 151 - if (parent.type === 'BinaryExpression' && parent.operator === '+') { 152 - parent = parent.parent 153 - continue 154 } 155 - 156 - if ( 157 - parent.type === 'JSXExpressionContainer' || 158 - parent.type === 'LogicalExpression' 159 - ) { 160 - parent = parent.parent 161 - continue 162 - } 163 - 164 - // Be conservative for other types. 165 - return 166 - } 167 - }, 168 - TemplateLiteral(node) { 169 - let parent = node.parent 170 - while (parent) { 171 - if (parent.type === 'JSXElement') { 172 - const tagName = getTagName(parent) 173 - if (isTextComponent(tagName)) { 174 - // We're good. 175 return 176 } 177 - if (tagName === 'Trans') { 178 - // Exit and rely on the traversal for <Trans> JSXElement (code below). 179 - // TODO: Maybe validate that it's present. 180 return 181 } 182 - const suggestedWrapper = suggestedTextWrappers[tagName] 183 - let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 184 - if (tagName !== 'View' && !suggestedWrapper) { 185 - message += 186 - ' If <' + 187 - tagName + 188 - '> is guaranteed to render <Text>, ' + 189 - 'rename it to <' + 190 - tagName + 191 - 'Text> or add it to impliedTextComponents.' 192 - } 193 - context.report({ 194 - node, 195 - message, 196 - }) 197 - return 198 - } 199 200 - if ( 201 - parent.type === 'CallExpression' && 202 - parent.callee.type === 'Identifier' && 203 - parent.callee.name === '_' 204 - ) { 205 - // This is a user-facing string, keep going up. 206 parent = parent.parent 207 continue 208 } 209 - 210 - if (parent.type === 'BinaryExpression' && parent.operator === '+') { 211 - parent = parent.parent 212 - continue 213 } 214 - 215 - if ( 216 - parent.type === 'JSXExpressionContainer' || 217 - parent.type === 'LogicalExpression' || 218 - parent.type === 'TaggedTemplateExpression' 219 - ) { 220 - parent = parent.parent 221 - continue 222 } 223 - 224 - // Be conservative for other types. 225 - return 226 - } 227 - }, 228 - JSXElement(node) { 229 - if (getTagName(node) !== 'Trans') { 230 - return 231 - } 232 - let parent = node.parent 233 - while (parent) { 234 - if (parent.type === 'JSXElement') { 235 - const tagName = getTagName(parent) 236 - if (isTextComponent(tagName)) { 237 - // We're good. 238 - return 239 - } 240 - if (tagName === 'Trans') { 241 - // Exit and rely on the traversal for this JSXElement. 242 - // TODO: Should nested <Trans> even be allowed? 243 - return 244 - } 245 - const suggestedWrapper = suggestedTextWrappers[tagName] 246 - let message = `Wrap this <Trans> in <${suggestedWrapper ?? 'Text'}>.` 247 - if (tagName !== 'View' && !suggestedWrapper) { 248 - message += 249 - ' If <' + 250 - tagName + 251 - '> is guaranteed to render <Text>, ' + 252 - 'rename it to <' + 253 - tagName + 254 - 'Text> or add it to impliedTextComponents.' 255 - } 256 - context.report({ 257 - node, 258 - message, 259 - }) 260 return 261 } 262 - 263 - if ( 264 - parent.type === 'JSXAttribute' && 265 - parent.name.type === 'JSXIdentifier' && 266 - parent.parent.type === 'JSXOpeningElement' && 267 - parent.parent.parent.type === 'JSXElement' 268 - ) { 269 - const tagName = getTagName(parent.parent.parent) 270 - const propName = parent.name.name 271 - if ( 272 - textProps.includes(tagName + ' ' + propName) || 273 - propName === 'text' || 274 - propName.endsWith('Text') 275 - ) { 276 - // We're good. 277 - return 278 - } 279 - const message = 280 - 'Wrap this <Trans> in <Text>.' + 281 - ' If `' + 282 - propName + 283 - '` is guaranteed to be wrapped in <Text>, ' + 284 - 'rename it to `' + 285 - propName + 286 - 'Text' + 287 - '` or add it to impliedTextProps.' 288 context.report({ 289 node, 290 - message, 291 }) 292 - return 293 } 294 - 295 - parent = parent.parent 296 - continue 297 - } 298 - }, 299 - ReturnStatement(node) { 300 - let fnScope = context.getScope() 301 - while (fnScope && fnScope.type !== 'function') { 302 - fnScope = fnScope.upper 303 - } 304 - if (!fnScope) { 305 - return 306 - } 307 - const fn = fnScope.block 308 - if (!fn.id || fn.id.type !== 'Identifier' || !fn.id.name) { 309 - return 310 - } 311 - if (!/^[A-Z]\w*Text$/.test(fn.id.name)) { 312 - return 313 - } 314 - if (!node.argument || node.argument.type !== 'JSXElement') { 315 - return 316 - } 317 - const openingEl = node.argument.openingElement 318 - if (openingEl.name.type !== 'JSXIdentifier') { 319 - return 320 - } 321 - const returnedComponentName = openingEl.name.name 322 - if (!isTextComponent(returnedComponentName)) { 323 - context.report({ 324 - node, 325 - message: 326 - 'Components ending with *Text must return <Text> or <SomeText>.', 327 - }) 328 - } 329 - }, 330 - } 331 }
··· 29 return reversedIdentifiers.reverse().join('.') 30 } 31 32 + module.exports = { 33 + meta: { 34 + type: 'problem', 35 + docs: { 36 + description: 'Enforce text strings are wrapped in <Text> components', 37 + }, 38 + schema: [ 39 + { 40 + type: 'object', 41 + properties: { 42 + impliedTextComponents: { 43 + type: 'array', 44 + items: {type: 'string'}, 45 + }, 46 + impliedTextProps: { 47 + type: 'array', 48 + items: {type: 'string'}, 49 + }, 50 + suggestedTextWrappers: { 51 + type: 'object', 52 + additionalProperties: {type: 'string'}, 53 + }, 54 + }, 55 + additionalProperties: false, 56 + }, 57 + ], 58 + }, 59 + create(context) { 60 + const options = context.options[0] || {} 61 + const impliedTextProps = options.impliedTextProps ?? [] 62 + const impliedTextComponents = options.impliedTextComponents ?? [] 63 + const suggestedTextWrappers = options.suggestedTextWrappers ?? {} 64 + const textProps = [...impliedTextProps] 65 + const textComponents = ['Text', ...impliedTextComponents] 66 67 + function isTextComponent(tagName) { 68 + return textComponents.includes(tagName) || tagName.endsWith('Text') 69 + } 70 71 + return { 72 + JSXText(node) { 73 + if (typeof node.value !== 'string' || hasOnlyLineBreak(node.value)) { 74 + return 75 + } 76 + let parent = node.parent 77 + while (parent) { 78 + if (parent.type === 'JSXElement') { 79 + const tagName = getTagName(parent) 80 + if (isTextComponent(tagName)) { 81 + // We're good. 82 + return 83 + } 84 + if (tagName === 'Trans') { 85 + // Exit and rely on the traversal for <Trans> JSXElement (code below). 86 + // TODO: Maybe validate that it's present. 87 + return 88 + } 89 + const suggestedWrapper = suggestedTextWrappers[tagName] 90 + let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 91 + if (tagName !== 'View' && !suggestedWrapper) { 92 + message += 93 + ' If <' + 94 + tagName + 95 + '> is guaranteed to render <Text>, ' + 96 + 'rename it to <' + 97 + tagName + 98 + 'Text> or add it to impliedTextComponents.' 99 + } 100 + context.report({ 101 + node, 102 + message, 103 + }) 104 return 105 } 106 + 107 + if ( 108 + parent.type === 'JSXAttribute' && 109 + parent.name.type === 'JSXIdentifier' && 110 + parent.parent.type === 'JSXOpeningElement' && 111 + parent.parent.parent.type === 'JSXElement' 112 + ) { 113 + const tagName = getTagName(parent.parent.parent) 114 + const propName = parent.name.name 115 + if ( 116 + textProps.includes(tagName + ' ' + propName) || 117 + propName === 'text' || 118 + propName.endsWith('Text') 119 + ) { 120 + // We're good. 121 + return 122 + } 123 + const message = 124 + 'Wrap this string in <Text>.' + 125 + ' If `' + 126 + propName + 127 + '` is guaranteed to be wrapped in <Text>, ' + 128 + 'rename it to `' + 129 + propName + 130 + 'Text' + 131 + '` or add it to impliedTextProps.' 132 + context.report({ 133 + node, 134 + message, 135 + }) 136 return 137 } 138 + 139 + parent = parent.parent 140 + continue 141 + } 142 + }, 143 + Literal(node) { 144 + if (typeof node.value !== 'string' && typeof node.value !== 'number') { 145 return 146 } 147 + let parent = node.parent 148 + while (parent) { 149 + if (parent.type === 'JSXElement') { 150 + const tagName = getTagName(parent) 151 + if (isTextComponent(tagName)) { 152 + // We're good. 153 + return 154 + } 155 + if (tagName === 'Trans') { 156 + // Exit and rely on the traversal for <Trans> JSXElement (code below). 157 + // TODO: Maybe validate that it's present. 158 + return 159 + } 160 + const suggestedWrapper = suggestedTextWrappers[tagName] 161 + let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 162 + if (tagName !== 'View' && !suggestedWrapper) { 163 + message += 164 + ' If <' + 165 + tagName + 166 + '> is guaranteed to render <Text>, ' + 167 + 'rename it to <' + 168 + tagName + 169 + 'Text> or add it to impliedTextComponents.' 170 + } 171 + context.report({ 172 + node, 173 + message, 174 + }) 175 + return 176 + } 177 178 + if (parent.type === 'BinaryExpression' && parent.operator === '+') { 179 + parent = parent.parent 180 + continue 181 + } 182 + 183 if ( 184 + parent.type === 'JSXExpressionContainer' || 185 + parent.type === 'LogicalExpression' 186 ) { 187 + parent = parent.parent 188 + continue 189 } 190 + 191 + // Be conservative for other types. 192 return 193 } 194 + }, 195 + TemplateLiteral(node) { 196 + let parent = node.parent 197 + while (parent) { 198 + if (parent.type === 'JSXElement') { 199 + const tagName = getTagName(parent) 200 + if (isTextComponent(tagName)) { 201 + // We're good. 202 + return 203 + } 204 + if (tagName === 'Trans') { 205 + // Exit and rely on the traversal for <Trans> JSXElement (code below). 206 + // TODO: Maybe validate that it's present. 207 + return 208 + } 209 + const suggestedWrapper = suggestedTextWrappers[tagName] 210 + let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 211 + if (tagName !== 'View' && !suggestedWrapper) { 212 + message += 213 + ' If <' + 214 + tagName + 215 + '> is guaranteed to render <Text>, ' + 216 + 'rename it to <' + 217 + tagName + 218 + 'Text> or add it to impliedTextComponents.' 219 + } 220 + context.report({ 221 + node, 222 + message, 223 + }) 224 return 225 } 226 + 227 + if ( 228 + parent.type === 'CallExpression' && 229 + parent.callee.type === 'Identifier' && 230 + parent.callee.name === '_' 231 + ) { 232 + // This is a user-facing string, keep going up. 233 + parent = parent.parent 234 + continue 235 } 236 + 237 + if (parent.type === 'BinaryExpression' && parent.operator === '+') { 238 + parent = parent.parent 239 + continue 240 + } 241 + 242 + if ( 243 + parent.type === 'JSXExpressionContainer' || 244 + parent.type === 'LogicalExpression' || 245 + parent.type === 'TaggedTemplateExpression' 246 + ) { 247 + parent = parent.parent 248 + continue 249 } 250 + 251 + // Be conservative for other types. 252 return 253 } 254 + }, 255 + JSXElement(node) { 256 + if (getTagName(node) !== 'Trans') { 257 + return 258 } 259 + let parent = node.parent 260 + while (parent) { 261 + if (parent.type === 'JSXElement') { 262 + const tagName = getTagName(parent) 263 + if (isTextComponent(tagName)) { 264 + // We're good. 265 + return 266 + } 267 + if (tagName === 'Trans') { 268 + // Exit and rely on the traversal for this JSXElement. 269 + // TODO: Should nested <Trans> even be allowed? 270 + return 271 + } 272 + const suggestedWrapper = suggestedTextWrappers[tagName] 273 + let message = `Wrap this <Trans> in <${suggestedWrapper ?? 'Text'}>.` 274 + if (tagName !== 'View' && !suggestedWrapper) { 275 + message += 276 + ' If <' + 277 + tagName + 278 + '> is guaranteed to render <Text>, ' + 279 + 'rename it to <' + 280 + tagName + 281 + 'Text> or add it to impliedTextComponents.' 282 + } 283 + context.report({ 284 + node, 285 + message, 286 + }) 287 return 288 } 289 + 290 + if ( 291 + parent.type === 'JSXAttribute' && 292 + parent.name.type === 'JSXIdentifier' && 293 + parent.parent.type === 'JSXOpeningElement' && 294 + parent.parent.parent.type === 'JSXElement' 295 + ) { 296 + const tagName = getTagName(parent.parent.parent) 297 + const propName = parent.name.name 298 + if ( 299 + textProps.includes(tagName + ' ' + propName) || 300 + propName === 'text' || 301 + propName.endsWith('Text') 302 + ) { 303 + // We're good. 304 + return 305 + } 306 + const message = 307 + 'Wrap this <Trans> in <Text>.' + 308 + ' If `' + 309 + propName + 310 + '` is guaranteed to be wrapped in <Text>, ' + 311 + 'rename it to `' + 312 + propName + 313 + 'Text' + 314 + '` or add it to impliedTextProps.' 315 + context.report({ 316 + node, 317 + message, 318 + }) 319 return 320 } 321 322 parent = parent.parent 323 continue 324 } 325 + }, 326 + ReturnStatement(node) { 327 + let fnScope = context.sourceCode.getScope(node) 328 + while (fnScope && fnScope.type !== 'function') { 329 + fnScope = fnScope.upper 330 } 331 + if (!fnScope) { 332 + return 333 } 334 + const fn = fnScope.block 335 + if (!fn.id || fn.id.type !== 'Identifier' || !fn.id.name) { 336 return 337 } 338 + if (!/^[A-Z]\w*Text$/.test(fn.id.name)) { 339 + return 340 + } 341 + if (!node.argument || node.argument.type !== 'JSXElement') { 342 + return 343 + } 344 + const openingEl = node.argument.openingElement 345 + if (openingEl.name.type !== 'JSXIdentifier') { 346 + return 347 + } 348 + const returnedComponentName = openingEl.name.name 349 + if (!isTextComponent(returnedComponentName)) { 350 context.report({ 351 node, 352 + message: 353 + 'Components ending with *Text must return <Text> or <SomeText>.', 354 }) 355 } 356 + }, 357 + } 358 + }, 359 }
+7 -1
eslint/index.js
··· 1 'use strict' 2 3 - module.exports = { 4 rules: { 5 'avoid-unwrapped-text': require('./avoid-unwrapped-text'), 6 'use-exact-imports': require('./use-exact-imports'), ··· 8 'use-prefixed-imports': require('./use-prefixed-imports'), 9 }, 10 }
··· 1 'use strict' 2 3 + const plugin = { 4 + meta: { 5 + name: 'eslint-plugin-bsky-internal', 6 + version: '1.0.0', 7 + }, 8 rules: { 9 'avoid-unwrapped-text': require('./avoid-unwrapped-text'), 10 'use-exact-imports': require('./use-exact-imports'), ··· 12 'use-prefixed-imports': require('./use-prefixed-imports'), 13 }, 14 } 15 + 16 + module.exports = plugin
+24 -15
eslint/use-exact-imports.js
··· 3 '@fortawesome/free-solid-svg-icons', 4 ] 5 6 - exports.create = function create(context) { 7 - return { 8 - ImportDeclaration(node) { 9 - const source = node.source 10 - if (typeof source.value !== 'string') { 11 - return 12 - } 13 - if (BANNED_IMPORTS.includes(source.value)) { 14 - context.report({ 15 - node, 16 - message: 17 - 'Import the specific thing you want instead of the entire package', 18 - }) 19 - } 20 }, 21 - } 22 }
··· 3 '@fortawesome/free-solid-svg-icons', 4 ] 5 6 + module.exports = { 7 + meta: { 8 + type: 'suggestion', 9 + docs: { 10 + description: 'Prevent importing entire icon packages', 11 }, 12 + schema: [], 13 + }, 14 + create(context) { 15 + return { 16 + ImportDeclaration(node) { 17 + const source = node.source 18 + if (typeof source.value !== 'string') { 19 + return 20 + } 21 + if (BANNED_IMPORTS.includes(source.value)) { 22 + context.report({ 23 + node, 24 + message: 25 + 'Import the specific thing you want instead of the entire package', 26 + }) 27 + } 28 + }, 29 + } 30 + }, 31 }
+4
eslint/use-prefixed-imports.js
··· 13 module.exports = { 14 meta: { 15 type: 'suggestion', 16 fixable: 'code', 17 }, 18 create(context) { 19 return {
··· 13 module.exports = { 14 meta: { 15 type: 'suggestion', 16 + docs: { 17 + description: 'Enforce using prefixed imports for internal paths', 18 + }, 19 fixable: 'code', 20 + schema: [], 21 }, 22 create(context) { 23 return {
+37 -27
eslint/use-typed-gates.js
··· 1 'use strict' 2 3 - exports.create = function create(context) { 4 - return { 5 - ImportSpecifier(node) { 6 - if ( 7 - !node.local || 8 - node.local.type !== 'Identifier' || 9 - node.local.name !== 'useGate' 10 - ) { 11 - return 12 - } 13 - if ( 14 - node.parent.type !== 'ImportDeclaration' || 15 - !node.parent.source || 16 - node.parent.source.type !== 'Literal' 17 - ) { 18 - return 19 - } 20 - const source = node.parent.source.value 21 - if (source.startsWith('statsig') || source.startsWith('@statsig')) { 22 - context.report({ 23 - node, 24 - message: 25 - "Use useGate() from '#/lib/statsig/statsig' instead of the one on npm.", 26 - }) 27 - } 28 - // TODO: Verify gate() call results aren't stored in variables. 29 }, 30 - } 31 }
··· 1 'use strict' 2 3 + module.exports = { 4 + meta: { 5 + type: 'suggestion', 6 + docs: { 7 + description: 8 + 'Enforce using internal statsig wrapper instead of npm package', 9 }, 10 + schema: [], 11 + }, 12 + create(context) { 13 + return { 14 + ImportSpecifier(node) { 15 + if ( 16 + !node.local || 17 + node.local.type !== 'Identifier' || 18 + node.local.name !== 'useGate' 19 + ) { 20 + return 21 + } 22 + if ( 23 + node.parent.type !== 'ImportDeclaration' || 24 + !node.parent.source || 25 + node.parent.source.type !== 'Literal' 26 + ) { 27 + return 28 + } 29 + const source = node.parent.source.value 30 + if (source.startsWith('statsig') || source.startsWith('@statsig')) { 31 + context.report({ 32 + node, 33 + message: 34 + "Use useGate() from '#/lib/statsig/statsig' instead of the one on npm.", 35 + }) 36 + } 37 + // TODO: Verify gate() call results aren't stored in variables. 38 + }, 39 + } 40 + }, 41 }
+5 -5
modules/bottom-sheet/src/BottomSheetNativeComponent.tsx
··· 11 import {useSafeAreaInsets} from 'react-native-safe-area-context' 12 import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core' 13 14 - import {isIOS} from '#/platform/detection' 15 import { 16 type BottomSheetState, 17 type BottomSheetViewProps, ··· 30 31 const NativeModule = requireNativeModule('BottomSheet') 32 33 - const isIOS15 = 34 Platform.OS === 'ios' && 35 // semvar - can be 3 segments, so can't use Number(Platform.Version) 36 Number(Platform.Version.split('.').at(0)) < 16 ··· 91 } 92 93 let extraStyles 94 - if (isIOS15 && this.state.viewHeight) { 95 const {viewHeight} = this.state 96 const cornerRadius = this.props.cornerRadius ?? 0 97 if (viewHeight < screenHeight / 2) { ··· 112 onStateChange={this.onStateChange} 113 extraStyles={extraStyles} 114 onLayout={e => { 115 - if (isIOS15) { 116 const {height} = e.nativeEvent.layout 117 this.setState({viewHeight: height}) 118 } ··· 153 const insets = useSafeAreaInsets() 154 const cornerRadius = rest.cornerRadius ?? 0 155 156 - const sheetHeight = isIOS ? screenHeight - insets.top : screenHeight 157 158 return ( 159 <NativeView
··· 11 import {useSafeAreaInsets} from 'react-native-safe-area-context' 12 import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core' 13 14 + import {IS_IOS} from '#/env' 15 import { 16 type BottomSheetState, 17 type BottomSheetViewProps, ··· 30 31 const NativeModule = requireNativeModule('BottomSheet') 32 33 + const IS_IOS15 = 34 Platform.OS === 'ios' && 35 // semvar - can be 3 segments, so can't use Number(Platform.Version) 36 Number(Platform.Version.split('.').at(0)) < 16 ··· 91 } 92 93 let extraStyles 94 + if (IS_IOS15 && this.state.viewHeight) { 95 const {viewHeight} = this.state 96 const cornerRadius = this.props.cornerRadius ?? 0 97 if (viewHeight < screenHeight / 2) { ··· 112 onStateChange={this.onStateChange} 113 extraStyles={extraStyles} 114 onLayout={e => { 115 + if (IS_IOS15) { 116 const {height} = e.nativeEvent.layout 117 this.setState({viewHeight: height}) 118 } ··· 153 const insets = useSafeAreaInsets() 154 const cornerRadius = rest.cornerRadius ?? 0 155 156 + const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight 157 158 return ( 159 <NativeView
+13 -11
package.json
··· 41 "test-watch": "NODE_ENV=test jest --watchAll", 42 "test-ci": "NODE_ENV=test jest --ci --forceExit --reporters=default --reporters=jest-junit", 43 "test-coverage": "NODE_ENV=test jest --coverage", 44 - "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src", 45 "lint-native": "swiftlint ./modules && ktlint ./modules", 46 "lint-native:fix": "swiftlint --fix ./modules && ktlint --format ./modules", 47 "typecheck": "tsc --project ./tsconfig.check.json", ··· 232 "@babel/core": "^7.26.0", 233 "@babel/preset-env": "^7.26.0", 234 "@babel/runtime": "^7.26.0", 235 "@expo/config-plugins": "~54.0.1", 236 "@lingui/cli": "^4.14.1", 237 "@lingui/macro": "^4.14.1", ··· 251 "@types/psl": "^1.1.1", 252 "@types/react": "^19.1.12", 253 "@types/react-dom": "^19.1.9", 254 - "@typescript-eslint/eslint-plugin": "^7.18.0", 255 - "@typescript-eslint/parser": "^7.18.0", 256 "babel-jest": "^29.7.0", 257 "babel-plugin-macros": "^3.1.0", 258 "babel-plugin-module-resolver": "^5.0.2", 259 "babel-plugin-react-compiler": "^19.1.0-rc.3", 260 "babel-preset-expo": "~54.0.0", 261 - "eslint": "^8.19.0", 262 "eslint-plugin-bsky-internal": "link:./eslint", 263 - "eslint-plugin-ft-flow": "^2.0.3", 264 - "eslint-plugin-import": "^2.31.0", 265 - "eslint-plugin-lingui": "^0.2.0", 266 - "eslint-plugin-react": "^7.33.2", 267 "eslint-plugin-react-compiler": "^19.1.0-rc.2", 268 - "eslint-plugin-react-native-a11y": "^3.3.0", 269 - "eslint-plugin-simple-import-sort": "^12.0.0", 270 "file-loader": "6.2.0", 271 "husky": "^8.0.3", 272 "is-ci": "^3.0.1", 273 "jest": "^29.7.0", ··· 282 "ts-node": "^10.9.1", 283 "ts-plugin-sort-import-suggestions": "^1.0.4", 284 "typescript": "^5.9.2", 285 "webpack-bundle-analyzer": "^4.10.1" 286 }, 287 "resolutions": { ··· 291 "**/@react-native-async-storage/async-storage": "2.2.0", 292 "**/expo-constants": "18.0.8", 293 "**/expo-device": "7.1.4", 294 - "**/zod": "3.23.8", 295 "**/multiformats": "9.9.0", 296 "unicode-segmenter": "0.14.5" 297 },
··· 41 "test-watch": "NODE_ENV=test jest --watchAll", 42 "test-ci": "NODE_ENV=test jest --ci --forceExit --reporters=default --reporters=jest-junit", 43 "test-coverage": "NODE_ENV=test jest --coverage", 44 + "lint": "eslint --cache --quiet src", 45 "lint-native": "swiftlint ./modules && ktlint ./modules", 46 "lint-native:fix": "swiftlint --fix ./modules && ktlint --format ./modules", 47 "typecheck": "tsc --project ./tsconfig.check.json", ··· 232 "@babel/core": "^7.26.0", 233 "@babel/preset-env": "^7.26.0", 234 "@babel/runtime": "^7.26.0", 235 + "@eslint/js": "^9.39.2", 236 "@expo/config-plugins": "~54.0.1", 237 "@lingui/cli": "^4.14.1", 238 "@lingui/macro": "^4.14.1", ··· 252 "@types/psl": "^1.1.1", 253 "@types/react": "^19.1.12", 254 "@types/react-dom": "^19.1.9", 255 "babel-jest": "^29.7.0", 256 "babel-plugin-macros": "^3.1.0", 257 "babel-plugin-module-resolver": "^5.0.2", 258 "babel-plugin-react-compiler": "^19.1.0-rc.3", 259 "babel-preset-expo": "~54.0.0", 260 + "eslint": "^9.39.2", 261 + "eslint-import-resolver-typescript": "^4.4.4", 262 "eslint-plugin-bsky-internal": "link:./eslint", 263 + "eslint-plugin-import-x": "^4.16.1", 264 + "eslint-plugin-lingui": "^0.11.0", 265 + "eslint-plugin-react": "^7.37.5", 266 "eslint-plugin-react-compiler": "^19.1.0-rc.2", 267 + "eslint-plugin-react-hooks": "^7.0.1", 268 + "eslint-plugin-react-native": "^5.0.0", 269 + "eslint-plugin-react-native-a11y": "^3.5.1", 270 + "eslint-plugin-simple-import-sort": "^12.1.1", 271 "file-loader": "6.2.0", 272 + "globals": "^17.0.0", 273 "husky": "^8.0.3", 274 "is-ci": "^3.0.1", 275 "jest": "^29.7.0", ··· 284 "ts-node": "^10.9.1", 285 "ts-plugin-sort-import-suggestions": "^1.0.4", 286 "typescript": "^5.9.2", 287 + "typescript-eslint": "^8.53.0", 288 "webpack-bundle-analyzer": "^4.10.1" 289 }, 290 "resolutions": { ··· 294 "**/@react-native-async-storage/async-storage": "2.2.0", 295 "**/expo-constants": "18.0.8", 296 "**/expo-device": "7.1.4", 297 "**/multiformats": "9.9.0", 298 "unicode-segmenter": "0.14.5" 299 },
+3 -3
src/App.native.tsx
··· 23 import {ThemeProvider} from '#/lib/ThemeContext' 24 import I18nProvider from '#/locale/i18nProvider' 25 import {logger} from '#/logger' 26 - import {isAndroid, isIOS} from '#/platform/detection' 27 import {Provider as A11yProvider} from '#/state/a11y' 28 import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' 29 import {Provider as DialogStateProvider} from '#/state/dialogs' ··· 69 import {ToastOutlet} from '#/components/Toast' 70 import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance' 71 import {prefetchAgeAssuranceConfig} from '#/ageAssurance' 72 import { 73 prefetchLiveEvents, 74 Provider as LiveEventsProvider, ··· 79 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 80 81 SplashScreen.preventAutoHideAsync() 82 - if (isIOS) { 83 SystemUI.setBackgroundColorAsync('black') 84 } 85 - if (isAndroid) { 86 // iOS is handled by the config plugin -sfn 87 ScreenOrientation.lockAsync( 88 ScreenOrientation.OrientationLock.PORTRAIT_UP,
··· 23 import {ThemeProvider} from '#/lib/ThemeContext' 24 import I18nProvider from '#/locale/i18nProvider' 25 import {logger} from '#/logger' 26 import {Provider as A11yProvider} from '#/state/a11y' 27 import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' 28 import {Provider as DialogStateProvider} from '#/state/dialogs' ··· 68 import {ToastOutlet} from '#/components/Toast' 69 import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance' 70 import {prefetchAgeAssuranceConfig} from '#/ageAssurance' 71 + import {IS_ANDROID, IS_IOS} from '#/env' 72 import { 73 prefetchLiveEvents, 74 Provider as LiveEventsProvider, ··· 79 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 80 81 SplashScreen.preventAutoHideAsync() 82 + if (IS_IOS) { 83 SystemUI.setBackgroundColorAsync('black') 84 } 85 + if (IS_ANDROID) { 86 // iOS is handled by the config plugin -sfn 87 ScreenOrientation.lockAsync( 88 ScreenOrientation.OrientationLock.PORTRAIT_UP,
+6 -6
src/Navigation.tsx
··· 44 import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig' 45 import {bskyTitle} from '#/lib/strings/headings' 46 import {logger} from '#/logger' 47 - import {isNative, isWeb} from '#/platform/detection' 48 import {useDisableVerifyEmailReminder} from '#/state/preferences/disable-verify-email-reminder' 49 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 50 import {useSession} from '#/state/session' ··· 140 EmailDialogScreenID, 141 useEmailDialogControl, 142 } from '#/components/dialogs/EmailDialog' 143 import {router} from '#/routes' 144 import {Referrer} from '../modules/expo-bluesky-swiss-army' 145 ··· 852 // native, since the home tab and the home screen are defined as initial routes, we don't need to return a state 853 // since it will be created by react-navigation. 854 if (path.includes('intent/')) { 855 - if (isNative) return 856 return buildStateObject('Flat', 'Home', params) 857 } 858 859 - if (isNative) { 860 if (name === 'Search') { 861 return buildStateObject('SearchTab', 'Search', params) 862 } ··· 933 ) 934 935 async function handlePushNotificationEntry() { 936 - if (!isNative) return 937 938 // deep links take precedence - on android, 939 // getLastNotificationResponseAsync returns a "notification" ··· 1085 navigationRef.dispatch( 1086 CommonActions.reset({ 1087 index: 0, 1088 - routes: [{name: isNative ? 'HomeTab' : 'Home'}], 1089 }), 1090 ) 1091 return Promise.race([ ··· 1119 initMs, 1120 }) 1121 1122 - if (isWeb) { 1123 const referrerInfo = Referrer.getReferrerInfo() 1124 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { 1125 logEvent('deepLink:referrerReceived', {
··· 44 import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig' 45 import {bskyTitle} from '#/lib/strings/headings' 46 import {logger} from '#/logger' 47 import {useDisableVerifyEmailReminder} from '#/state/preferences/disable-verify-email-reminder' 48 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 49 import {useSession} from '#/state/session' ··· 139 EmailDialogScreenID, 140 useEmailDialogControl, 141 } from '#/components/dialogs/EmailDialog' 142 + import {IS_NATIVE, IS_WEB} from '#/env' 143 import {router} from '#/routes' 144 import {Referrer} from '../modules/expo-bluesky-swiss-army' 145 ··· 852 // native, since the home tab and the home screen are defined as initial routes, we don't need to return a state 853 // since it will be created by react-navigation. 854 if (path.includes('intent/')) { 855 + if (IS_NATIVE) return 856 return buildStateObject('Flat', 'Home', params) 857 } 858 859 + if (IS_NATIVE) { 860 if (name === 'Search') { 861 return buildStateObject('SearchTab', 'Search', params) 862 } ··· 933 ) 934 935 async function handlePushNotificationEntry() { 936 + if (!IS_NATIVE) return 937 938 // deep links take precedence - on android, 939 // getLastNotificationResponseAsync returns a "notification" ··· 1085 navigationRef.dispatch( 1086 CommonActions.reset({ 1087 index: 0, 1088 + routes: [{name: IS_NATIVE ? 'HomeTab' : 'Home'}], 1089 }), 1090 ) 1091 return Promise.race([ ··· 1119 initMs, 1120 }) 1121 1122 + if (IS_WEB) { 1123 const referrerInfo = Referrer.getReferrerInfo() 1124 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { 1125 logEvent('deepLink:referrerReceived', {
+5 -5
src/ageAssurance/components/NoAccessScreen.tsx
··· 10 } from '#/lib/hooks/useCreateSupportLink' 11 import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 12 import {logger} from '#/logger' 13 - import {isWeb} from '#/platform/detection' 14 - import {isNative} from '#/platform/detection' 15 import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 16 import {useSessionApi} from '#/state/session' 17 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' ··· 38 isLegacyBirthdateBug, 39 useAgeAssuranceRegionConfig, 40 } from '#/ageAssurance/util' 41 import {useDeviceGeolocationApi} from '#/geolocation' 42 43 const textStyles = [a.text_md, a.leading_snug] ··· 74 }, []) 75 76 const onPressLogout = useCallback(() => { 77 - if (isWeb) { 78 // We're switching accounts, which remounts the entire app. 79 // On mobile, this gets us Home, but on the web we also need reset the URL. 80 // We can't change the URL via a navigate() call because the navigator ··· 139 contentContainerStyle={[ 140 a.px_2xl, 141 { 142 - paddingTop: isWeb 143 ? a.p_5xl.padding 144 : insets.top + a.p_2xl.padding, 145 paddingBottom: 100, ··· 359 )} 360 361 <View style={[a.gap_xs]}> 362 - {isNative && ( 363 <> 364 <Admonition> 365 <Trans>
··· 10 } from '#/lib/hooks/useCreateSupportLink' 11 import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 12 import {logger} from '#/logger' 13 import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 14 import {useSessionApi} from '#/state/session' 15 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' ··· 36 isLegacyBirthdateBug, 37 useAgeAssuranceRegionConfig, 38 } from '#/ageAssurance/util' 39 + import {IS_WEB} from '#/env' 40 + import {IS_NATIVE} from '#/env' 41 import {useDeviceGeolocationApi} from '#/geolocation' 42 43 const textStyles = [a.text_md, a.leading_snug] ··· 74 }, []) 75 76 const onPressLogout = useCallback(() => { 77 + if (IS_WEB) { 78 // We're switching accounts, which remounts the entire app. 79 // On mobile, this gets us Home, but on the web we also need reset the URL. 80 // We can't change the URL via a navigate() call because the navigator ··· 139 contentContainerStyle={[ 140 a.px_2xl, 141 { 142 + paddingTop: IS_WEB 143 ? a.p_5xl.padding 144 : insets.top + a.p_2xl.padding, 145 paddingBottom: 100, ··· 359 )} 360 361 <View style={[a.gap_xs]}> 362 + {IS_NATIVE && ( 363 <> 364 <Admonition> 365 <Trans>
+4 -4
src/ageAssurance/components/RedirectOverlay.tsx
··· 15 import {retry} from '#/lib/async/retry' 16 import {wait} from '#/lib/async/wait' 17 import {parseLinkingUrl} from '#/lib/parseLinkingUrl' 18 - import {isWeb} from '#/platform/detection' 19 - import {isIOS} from '#/platform/detection' 20 import {useAgent, useSession} from '#/state/session' 21 import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' 22 import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' ··· 28 import {Text} from '#/components/Typography' 29 import {refetchAgeAssuranceServerState} from '#/ageAssurance' 30 import {logger} from '#/ageAssurance' 31 32 export type RedirectOverlayState = { 33 result: 'success' | 'unknown' ··· 92 actorDid: params.get('actorDid') ?? undefined, 93 }) 94 95 - if (isWeb) { 96 // Clear the URL parameters so they don't re-trigger 97 history.pushState(null, '', '/') 98 } ··· 149 // setting a zIndex when using FullWindowOverlay on iOS 150 // means the taps pass straight through to the underlying content (???) 151 // so don't set it on iOS. FullWindowOverlay already does the job. 152 - !isIOS && {zIndex: 9999}, 153 t.atoms.bg, 154 gtMobile ? a.p_2xl : a.p_xl, 155 a.align_center,
··· 15 import {retry} from '#/lib/async/retry' 16 import {wait} from '#/lib/async/wait' 17 import {parseLinkingUrl} from '#/lib/parseLinkingUrl' 18 import {useAgent, useSession} from '#/state/session' 19 import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' 20 import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' ··· 26 import {Text} from '#/components/Typography' 27 import {refetchAgeAssuranceServerState} from '#/ageAssurance' 28 import {logger} from '#/ageAssurance' 29 + import {IS_WEB} from '#/env' 30 + import {IS_IOS} from '#/env' 31 32 export type RedirectOverlayState = { 33 result: 'success' | 'unknown' ··· 92 actorDid: params.get('actorDid') ?? undefined, 93 }) 94 95 + if (IS_WEB) { 96 // Clear the URL parameters so they don't re-trigger 97 history.pushState(null, '', '/') 98 } ··· 149 // setting a zIndex when using FullWindowOverlay on iOS 150 // means the taps pass straight through to the underlying content (???) 151 // so don't set it on iOS. FullWindowOverlay already does the job. 152 + !IS_IOS && {zIndex: 9999}, 153 t.atoms.bg, 154 gtMobile ? a.p_2xl : a.p_xl, 155 a.align_center,
+4 -4
src/alf/fonts.ts
··· 1 import {type TextStyle} from 'react-native' 2 3 - import {isAndroid, isWeb} from '#/platform/detection' 4 import {type Device, device} from '#/storage' 5 6 const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"` ··· 39 */ 40 export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') { 41 if (fontFamily === 'theme') { 42 - if (isAndroid) { 43 style.fontFamily = 44 { 45 400: 'Inter-Regular', ··· 71 } 72 } 73 74 - if (isWeb) { 75 // fallback families only supported on web 76 style.fontFamily += `, ${WEB_FONT_FAMILIES}` 77 } ··· 83 style.fontVariant = (style.fontVariant || []).concat('no-contextual') 84 } else { 85 // fallback families only supported on web 86 - if (isWeb) { 87 style.fontFamily = style.fontFamily || WEB_FONT_FAMILIES 88 } 89
··· 1 import {type TextStyle} from 'react-native' 2 3 + import {IS_ANDROID, IS_WEB} from '#/env' 4 import {type Device, device} from '#/storage' 5 6 const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"` ··· 39 */ 40 export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') { 41 if (fontFamily === 'theme') { 42 + if (IS_ANDROID) { 43 style.fontFamily = 44 { 45 400: 'Inter-Regular', ··· 71 } 72 } 73 74 + if (IS_WEB) { 75 // fallback families only supported on web 76 style.fontFamily += `, ${WEB_FONT_FAMILIES}` 77 } ··· 83 style.fontVariant = (style.fontVariant || []).concat('no-contextual') 84 } else { 85 // fallback families only supported on web 86 + if (IS_WEB) { 87 style.fontFamily = style.fontFamily || WEB_FONT_FAMILIES 88 } 89
+4 -4
src/alf/typography.tsx
··· 4 import {UITextView} from 'react-native-uitextview' 5 import createEmojiRegex from 'emoji-regex' 6 7 - import {isNative} from '#/platform/detection' 8 - import {isIOS} from '#/platform/detection' 9 import {type Alf, applyFonts, atoms, flatten} from '#/alf' 10 11 /** 12 * Ensures that `lineHeight` defaults to a relative value of `1`, or applies ··· 34 if (s.lineHeight !== 0 && s.lineHeight <= 2) { 35 s.lineHeight = Math.round(s.fontSize * s.lineHeight) 36 } 37 - } else if (!isNative) { 38 s.lineHeight = s.fontSize 39 } 40 ··· 81 props: Omit<TextProps, 'children'> = {}, 82 emoji: boolean, 83 ) { 84 - if (!isIOS || !emoji) { 85 return children 86 } 87 return Children.map(children, child => {
··· 4 import {UITextView} from 'react-native-uitextview' 5 import createEmojiRegex from 'emoji-regex' 6 7 import {type Alf, applyFonts, atoms, flatten} from '#/alf' 8 + import {IS_NATIVE} from '#/env' 9 + import {IS_IOS} from '#/env' 10 11 /** 12 * Ensures that `lineHeight` defaults to a relative value of `1`, or applies ··· 34 if (s.lineHeight !== 0 && s.lineHeight <= 2) { 35 s.lineHeight = Math.round(s.fontSize * s.lineHeight) 36 } 37 + } else if (!IS_NATIVE) { 38 s.lineHeight = s.fontSize 39 } 40 ··· 81 props: Omit<TextProps, 'children'> = {}, 82 emoji: boolean, 83 ) { 84 + if (!IS_IOS || !emoji) { 85 return children 86 } 87 return Children.map(children, child => {
+2 -2
src/alf/util/systemUI.ts
··· 2 import {type Theme} from '@bsky.app/alf' 3 4 import {logger} from '#/logger' 5 - import {isAndroid} from '#/platform/detection' 6 7 export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { 8 - if (isAndroid) { 9 try { 10 if (themeType === 'theme') { 11 SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
··· 2 import {type Theme} from '@bsky.app/alf' 3 4 import {logger} from '#/logger' 5 + import {IS_ANDROID} from '#/env' 6 7 export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { 8 + if (IS_ANDROID) { 9 try { 10 if (themeType === 'theme') { 11 SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
+2 -2
src/alf/util/useColorModeTheme.ts
··· 2 import {type ColorSchemeName, useColorScheme} from 'react-native' 3 import {type ThemeName} from '@bsky.app/alf' 4 5 - import {isWeb} from '#/platform/detection' 6 import {useThemePrefs} from '#/state/shell' 7 import {dark, dim, light} from '#/alf/themes' 8 9 export function useColorModeTheme(): ThemeName { 10 const theme = useThemeName() ··· 40 41 function updateDocument(theme: ThemeName) { 42 // @ts-ignore web only 43 - if (isWeb && typeof window !== 'undefined') { 44 // @ts-ignore web only 45 const html = window.document.documentElement 46 // @ts-ignore web only
··· 2 import {type ColorSchemeName, useColorScheme} from 'react-native' 3 import {type ThemeName} from '@bsky.app/alf' 4 5 import {useThemePrefs} from '#/state/shell' 6 import {dark, dim, light} from '#/alf/themes' 7 + import {IS_WEB} from '#/env' 8 9 export function useColorModeTheme(): ThemeName { 10 const theme = useThemeName() ··· 40 41 function updateDocument(theme: ThemeName) { 42 // @ts-ignore web only 43 + if (IS_WEB && typeof window !== 'undefined') { 44 // @ts-ignore web only 45 const html = window.document.documentElement 46 // @ts-ignore web only
+4 -4
src/components/BlockedGeoOverlay.tsx
··· 5 import {useLingui} from '@lingui/react' 6 7 import {logger} from '#/logger' 8 - import {isWeb} from '#/platform/detection' 9 - import {useDeviceGeolocationApi} from '#/state/geolocation' 10 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 12 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 20 import * as Toast from '#/components/Toast' 21 import {Text} from '#/components/Typography' 22 import {BottomSheetOutlet} from '#/../modules/bottom-sheet' 23 24 export function BlockedGeoOverlay() { 25 const t = useTheme() ··· 70 contentContainerStyle={[ 71 a.px_2xl, 72 { 73 - paddingTop: isWeb ? a.p_5xl.padding : insets.top + a.p_2xl.padding, 74 paddingBottom: 100, 75 }, 76 ]}> ··· 117 ))} 118 </View> 119 120 - {!isWeb && ( 121 <> 122 <View style={[a.pt_2xl]}> 123 <Divider />
··· 5 import {useLingui} from '@lingui/react' 6 7 import {logger} from '#/logger' 8 + import {useDeviceGeolocationApi} from '#/geolocation' 9 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 10 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 11 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 19 import * as Toast from '#/components/Toast' 20 import {Text} from '#/components/Typography' 21 import {BottomSheetOutlet} from '#/../modules/bottom-sheet' 22 + import {IS_WEB} from '#/env' 23 24 export function BlockedGeoOverlay() { 25 const t = useTheme() ··· 70 contentContainerStyle={[ 71 a.px_2xl, 72 { 73 + paddingTop: IS_WEB ? a.p_5xl.padding : insets.top + a.p_2xl.padding, 74 paddingBottom: 100, 75 }, 76 ]}> ··· 117 ))} 118 </View> 119 120 + {!IS_WEB && ( 121 <> 122 <View style={[a.pt_2xl]}> 123 <Divider />
+5 -5
src/components/ContextMenu/index.tsx
··· 49 import {useHaptics} from '#/lib/haptics' 50 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 51 import {logger} from '#/logger' 52 - import {isAndroid, isIOS} from '#/platform/detection' 53 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 54 import {atoms as a, platform, tokens, useTheme} from '#/alf' 55 import { ··· 72 import {useInteractionState} from '#/components/hooks/useInteractionState' 73 import {createPortalGroup} from '#/components/Portal' 74 import {Text} from '#/components/Typography' 75 import {Backdrop} from './Backdrop' 76 77 export { ··· 82 const {Provider: PortalProvider, Outlet, Portal} = createPortalGroup() 83 84 const SPRING_IN: WithSpringConfig = { 85 - mass: isIOS ? 1.25 : 0.75, 86 damping: 50, 87 stiffness: 1100, 88 restDisplacementThreshold: 0.01, 89 } 90 91 const SPRING_OUT: WithSpringConfig = { 92 - mass: isIOS ? 1.25 : 0.75, 93 damping: 150, 94 stiffness: 1000, 95 restDisplacementThreshold: 0.01, ··· 210 ) 211 212 useEffect(() => { 213 - if (isAndroid && context.isOpen) { 214 const listener = BackHandler.addEventListener('hardwareBackPress', () => { 215 context.close() 216 return true ··· 332 <GestureDetector gesture={composedGestures}> 333 <View ref={ref} style={[{opacity: context.isOpen ? 0 : 1}, style]}> 334 {children({ 335 - isNative: true, 336 control: {isOpen: context.isOpen, open}, 337 state: { 338 pressed: false,
··· 49 import {useHaptics} from '#/lib/haptics' 50 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 51 import {logger} from '#/logger' 52 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 53 import {atoms as a, platform, tokens, useTheme} from '#/alf' 54 import { ··· 71 import {useInteractionState} from '#/components/hooks/useInteractionState' 72 import {createPortalGroup} from '#/components/Portal' 73 import {Text} from '#/components/Typography' 74 + import {IS_ANDROID, IS_IOS} from '#/env' 75 import {Backdrop} from './Backdrop' 76 77 export { ··· 82 const {Provider: PortalProvider, Outlet, Portal} = createPortalGroup() 83 84 const SPRING_IN: WithSpringConfig = { 85 + mass: IS_IOS ? 1.25 : 0.75, 86 damping: 50, 87 stiffness: 1100, 88 restDisplacementThreshold: 0.01, 89 } 90 91 const SPRING_OUT: WithSpringConfig = { 92 + mass: IS_IOS ? 1.25 : 0.75, 93 damping: 150, 94 stiffness: 1000, 95 restDisplacementThreshold: 0.01, ··· 210 ) 211 212 useEffect(() => { 213 + if (IS_ANDROID && context.isOpen) { 214 const listener = BackHandler.addEventListener('hardwareBackPress', () => { 215 context.close() 216 return true ··· 332 <GestureDetector gesture={composedGestures}> 333 <View ref={ref} style={[{opacity: context.isOpen ? 0 : 1}, style]}> 334 {children({ 335 + IS_NATIVE: true, 336 control: {isOpen: context.isOpen, open}, 337 state: { 338 pressed: false,
+2 -2
src/components/ContextMenu/types.ts
··· 85 } 86 export type TriggerChildProps = 87 | { 88 - isNative: true 89 control: { 90 isOpen: boolean 91 open: (mode: 'full' | 'auxiliary-only') => void ··· 115 } 116 } 117 | { 118 - isNative: false 119 control: Dialog.DialogOuterProps['control'] 120 state: { 121 hovered: false
··· 85 } 86 export type TriggerChildProps = 87 | { 88 + IS_NATIVE: true 89 control: { 90 isOpen: boolean 91 open: (mode: 'full' | 'auxiliary-only') => void ··· 115 } 116 } 117 | { 118 + IS_NATIVE: false 119 control: Dialog.DialogOuterProps['control'] 120 state: { 121 hovered: false
+1 -1
src/components/Dialog/context.ts
··· 18 19 export const Context = createContext<DialogContextProps>({ 20 close: () => {}, 21 - isNativeDialog: false, 22 nativeSnapPoint: BottomSheetSnapPoint.Hidden, 23 disableDrag: false, 24 setDisableDrag: () => {},
··· 18 19 export const Context = createContext<DialogContextProps>({ 20 close: () => {}, 21 + IS_NATIVEDialog: false, 22 nativeSnapPoint: BottomSheetSnapPoint.Hidden, 23 disableDrag: false, 24 setDisableDrag: () => {},
+11 -11
src/components/Dialog/index.tsx
··· 26 import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 27 import {ScrollProvider} from '#/lib/ScrollContext' 28 import {logger} from '#/logger' 29 - import {isAndroid, isIOS} from '#/platform/detection' 30 import {useA11y} from '#/state/a11y' 31 import {useDialogStateControlContext} from '#/state/dialogs' 32 import {List, type ListMethods, type ListProps} from '#/view/com/util/List' ··· 39 type DialogOuterProps, 40 } from '#/components/Dialog/types' 41 import {createInput} from '#/components/forms/TextField' 42 import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet' 43 import { 44 type BottomSheetSnapPointChangeEvent, ··· 154 const context = React.useMemo( 155 () => ({ 156 close, 157 - isNativeDialog: true, 158 nativeSnapPoint: snapPoint, 159 disableDrag, 160 setDisableDrag, ··· 209 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 210 const insets = useSafeAreaInsets() 211 212 - useEnableKeyboardController(isIOS) 213 214 const [keyboardHeight, setKeyboardHeight] = React.useState(0) 215 ··· 224 ) 225 226 let paddingBottom = 0 227 - if (isIOS) { 228 paddingBottom += keyboardHeight / 4 229 if (nativeSnapPoint === BottomSheetSnapPoint.Full) { 230 paddingBottom += insets.bottom + tokens.space.md ··· 240 } 241 242 const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => { 243 - if (!isAndroid) { 244 return 245 } 246 const {contentOffset} = e.nativeEvent ··· 260 contentContainerStyle, 261 ]} 262 ref={ref} 263 - showsVerticalScrollIndicator={isAndroid ? false : undefined} 264 {...props} 265 bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} 266 bottomOffset={30} 267 scrollEventThrottle={50} 268 - onScroll={isAndroid ? onScroll : undefined} 269 keyboardShouldPersistTaps="handled" 270 // TODO: figure out why this positions the header absolutely (rather than stickily) 271 // on Android. fine to disable for now, because we don't have any ··· 289 const insets = useSafeAreaInsets() 290 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 291 292 - useEnableKeyboardController(isIOS) 293 294 const onScroll = (e: ScrollEvent) => { 295 'worklet' 296 - if (!isAndroid) { 297 return 298 } 299 const {contentOffset} = e ··· 311 bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} 312 ListFooterComponent={<View style={{height: insets.bottom + 100}} />} 313 ref={ref} 314 - showsVerticalScrollIndicator={isAndroid ? false : undefined} 315 {...props} 316 style={[a.h_full, style]} 317 /> ··· 326 const {height} = useReanimatedKeyboardAnimation() 327 328 const animatedStyle = useAnimatedStyle(() => { 329 - if (!isIOS) return {} 330 return { 331 transform: [{translateY: Math.min(0, height.get() + bottom - 10)}], 332 }
··· 26 import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 27 import {ScrollProvider} from '#/lib/ScrollContext' 28 import {logger} from '#/logger' 29 import {useA11y} from '#/state/a11y' 30 import {useDialogStateControlContext} from '#/state/dialogs' 31 import {List, type ListMethods, type ListProps} from '#/view/com/util/List' ··· 38 type DialogOuterProps, 39 } from '#/components/Dialog/types' 40 import {createInput} from '#/components/forms/TextField' 41 + import {IS_ANDROID, IS_IOS} from '#/env' 42 import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet' 43 import { 44 type BottomSheetSnapPointChangeEvent, ··· 154 const context = React.useMemo( 155 () => ({ 156 close, 157 + IS_NATIVEDialog: true, 158 nativeSnapPoint: snapPoint, 159 disableDrag, 160 setDisableDrag, ··· 209 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 210 const insets = useSafeAreaInsets() 211 212 + useEnableKeyboardController(IS_IOS) 213 214 const [keyboardHeight, setKeyboardHeight] = React.useState(0) 215 ··· 224 ) 225 226 let paddingBottom = 0 227 + if (IS_IOS) { 228 paddingBottom += keyboardHeight / 4 229 if (nativeSnapPoint === BottomSheetSnapPoint.Full) { 230 paddingBottom += insets.bottom + tokens.space.md ··· 240 } 241 242 const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => { 243 + if (!IS_ANDROID) { 244 return 245 } 246 const {contentOffset} = e.nativeEvent ··· 260 contentContainerStyle, 261 ]} 262 ref={ref} 263 + showsVerticalScrollIndicator={IS_ANDROID ? false : undefined} 264 {...props} 265 bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} 266 bottomOffset={30} 267 scrollEventThrottle={50} 268 + onScroll={IS_ANDROID ? onScroll : undefined} 269 keyboardShouldPersistTaps="handled" 270 // TODO: figure out why this positions the header absolutely (rather than stickily) 271 // on Android. fine to disable for now, because we don't have any ··· 289 const insets = useSafeAreaInsets() 290 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 291 292 + useEnableKeyboardController(IS_IOS) 293 294 const onScroll = (e: ScrollEvent) => { 295 'worklet' 296 + if (!IS_ANDROID) { 297 return 298 } 299 const {contentOffset} = e ··· 311 bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} 312 ListFooterComponent={<View style={{height: insets.bottom + 100}} />} 313 ref={ref} 314 + showsVerticalScrollIndicator={IS_ANDROID ? false : undefined} 315 {...props} 316 style={[a.h_full, style]} 317 /> ··· 326 const {height} = useReanimatedKeyboardAnimation() 327 328 const animatedStyle = useAnimatedStyle(() => { 329 + if (!IS_IOS) return {} 330 return { 331 transform: [{translateY: Math.min(0, height.get() + bottom - 10)}], 332 }
+1 -1
src/components/Dialog/index.web.tsx
··· 99 const context = React.useMemo( 100 () => ({ 101 close, 102 - isNativeDialog: false, 103 nativeSnapPoint: 0, 104 disableDrag: false, 105 setDisableDrag: () => {},
··· 99 const context = React.useMemo( 100 () => ({ 101 close, 102 + IS_NATIVEDialog: false, 103 nativeSnapPoint: 0, 104 disableDrag: false, 105 setDisableDrag: () => {},
+2 -2
src/components/Dialog/sheet-wrapper.ts
··· 1 import {useCallback} from 'react' 2 import {SystemBars} from 'react-native-edge-to-edge' 3 4 - import {isIOS} from '#/platform/detection' 5 6 /** 7 * If we're calling a system API like the image picker that opens a sheet ··· 9 */ 10 export function useSheetWrapper() { 11 return useCallback(async <T>(promise: Promise<T>): Promise<T> => { 12 - if (isIOS) { 13 const entry = SystemBars.pushStackEntry({ 14 style: { 15 statusBar: 'light',
··· 1 import {useCallback} from 'react' 2 import {SystemBars} from 'react-native-edge-to-edge' 3 4 + import {IS_IOS} from '#/env' 5 6 /** 7 * If we're calling a system API like the image picker that opens a sheet ··· 9 */ 10 export function useSheetWrapper() { 11 return useCallback(async <T>(promise: Promise<T>): Promise<T> => { 12 + if (IS_IOS) { 13 const entry = SystemBars.pushStackEntry({ 14 style: { 15 statusBar: 'light',
+1 -1
src/components/Dialog/types.ts
··· 39 40 export type DialogContextProps = { 41 close: DialogControlProps['close'] 42 - isNativeDialog: boolean 43 nativeSnapPoint: BottomSheetSnapPoint 44 disableDrag: boolean 45 setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
··· 39 40 export type DialogContextProps = { 41 close: DialogControlProps['close'] 42 + IS_NATIVEDialog: boolean 43 nativeSnapPoint: BottomSheetSnapPoint 44 disableDrag: boolean 45 setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
+3 -3
src/components/FeedInterstitials.tsx
··· 10 import {logEvent, useGate} from '#/lib/statsig/statsig' 11 import {logger} from '#/logger' 12 import {type MetricEvents} from '#/logger/metrics' 13 - import {isIOS} from '#/platform/detection' 14 import {useHideSimilarAccountsRecomm} from '#/state/preferences/hide-similar-accounts-recommendations' 15 import {useModerationOpts} from '#/state/preferences/moderation-opts' 16 import {useGetPopularFeedsQuery} from '#/state/queries/feed' ··· 40 import {InlineLinkText} from '#/components/Link' 41 import * as ProfileCard from '#/components/ProfileCard' 42 import {Text} from '#/components/Typography' 43 import type * as bsky from '#/types/bsky' 44 import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' 45 import {ProgressGuideList} from './ProgressGuide/List' ··· 697 t.atoms.border_contrast_low, 698 t.atoms.bg_contrast_25, 699 ]} 700 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 701 <View 702 style={[ 703 a.px_lg, ··· 706 a.align_center, 707 a.justify_between, 708 ]} 709 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 710 <Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}> 711 {isFeedContext ? ( 712 <Trans>Suggested for you</Trans>
··· 10 import {logEvent, useGate} from '#/lib/statsig/statsig' 11 import {logger} from '#/logger' 12 import {type MetricEvents} from '#/logger/metrics' 13 import {useHideSimilarAccountsRecomm} from '#/state/preferences/hide-similar-accounts-recommendations' 14 import {useModerationOpts} from '#/state/preferences/moderation-opts' 15 import {useGetPopularFeedsQuery} from '#/state/queries/feed' ··· 39 import {InlineLinkText} from '#/components/Link' 40 import * as ProfileCard from '#/components/ProfileCard' 41 import {Text} from '#/components/Typography' 42 + import {IS_IOS} from '#/env' 43 import type * as bsky from '#/types/bsky' 44 import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' 45 import {ProgressGuideList} from './ProgressGuide/List' ··· 697 t.atoms.border_contrast_low, 698 t.atoms.bg_contrast_25, 699 ]} 700 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 701 <View 702 style={[ 703 a.px_lg, ··· 706 a.align_center, 707 a.justify_between, 708 ]} 709 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 710 <Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}> 711 {isFeedContext ? ( 712 <Trans>Suggested for you</Trans>
+3 -3
src/components/InterestTabs.tsx
··· 9 import {useLingui} from '@lingui/react' 10 11 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 12 - import {isWeb} from '#/platform/detection' 13 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 14 import {DraggableScrollView} from '#/view/com/pager/DraggableScrollView' 15 import {atoms as a, tokens, useTheme, web} from '#/alf' ··· 20 ArrowRight_Stroke2_Corner0_Rounded as ArrowRight, 21 } from '#/components/icons/Arrow' 22 import {Text} from '#/components/Typography' 23 24 /** 25 * Tab component that automatically scrolls the selected tab into view - used for interests ··· 239 ) 240 })} 241 </DraggableScrollView> 242 - {isWeb && canScrollLeft && ( 243 <View 244 style={[ 245 a.absolute, ··· 273 </Button> 274 </View> 275 )} 276 - {isWeb && canScrollRight && ( 277 <View 278 style={[ 279 a.absolute,
··· 9 import {useLingui} from '@lingui/react' 10 11 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 12 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 13 import {DraggableScrollView} from '#/view/com/pager/DraggableScrollView' 14 import {atoms as a, tokens, useTheme, web} from '#/alf' ··· 19 ArrowRight_Stroke2_Corner0_Rounded as ArrowRight, 20 } from '#/components/icons/Arrow' 21 import {Text} from '#/components/Typography' 22 + import {IS_WEB} from '#/env' 23 24 /** 25 * Tab component that automatically scrolls the selected tab into view - used for interests ··· 239 ) 240 })} 241 </DraggableScrollView> 242 + {IS_WEB && canScrollLeft && ( 243 <View 244 style={[ 245 a.absolute, ··· 273 </Button> 274 </View> 275 )} 276 + {IS_WEB && canScrollRight && ( 277 <View 278 style={[ 279 a.absolute,
+3 -3
src/components/InternationalPhoneCodeSelect.tsx
··· 10 INTERNATIONAL_TELEPHONE_CODES, 11 } from '#/lib/international-telephone-codes' 12 import {regionName} from '#/locale/helpers' 13 - import {isWeb} from '#/platform/detection' 14 import {atoms as a, web} from '#/alf' 15 import * as Select from '#/components/Select' 16 import {useGeolocation} from '#/geolocation' 17 18 /** ··· 84 <Select.Item value={item.value} label={item.label}> 85 <Select.ItemIndicator /> 86 <Select.ItemText style={[a.flex_1]} emoji> 87 - {isWeb ? <Flag {...item} /> : item.unicodeFlag + ' '} 88 {item.name} 89 </Select.ItemText> 90 <Select.ItemText style={[a.text_right]}> ··· 101 } 102 103 function Flag({unicodeFlag, svgFlag}: {unicodeFlag: string; svgFlag: any}) { 104 - if (isWeb) { 105 return ( 106 <Image 107 source={svgFlag}
··· 10 INTERNATIONAL_TELEPHONE_CODES, 11 } from '#/lib/international-telephone-codes' 12 import {regionName} from '#/locale/helpers' 13 import {atoms as a, web} from '#/alf' 14 import * as Select from '#/components/Select' 15 + import {IS_WEB} from '#/env' 16 import {useGeolocation} from '#/geolocation' 17 18 /** ··· 84 <Select.Item value={item.value} label={item.label}> 85 <Select.ItemIndicator /> 86 <Select.ItemText style={[a.flex_1]} emoji> 87 + {IS_WEB ? <Flag {...item} /> : item.unicodeFlag + ' '} 88 {item.name} 89 </Select.ItemText> 90 <Select.ItemText style={[a.text_right]}> ··· 101 } 102 103 function Flag({unicodeFlag, svgFlag}: {unicodeFlag: string; svgFlag: any}) { 104 + if (IS_WEB) { 105 return ( 106 <Image 107 source={svgFlag}
+4 -4
src/components/Layout/Header/index.tsx
··· 6 7 import {HITSLOP_30} from '#/lib/constants' 8 import {type NavigationProp} from '#/lib/routes/types' 9 - import {isIOS} from '#/platform/detection' 10 import {useSetDrawerOpen} from '#/state/shell' 11 import { 12 atoms as a, ··· 29 } from '#/components/Layout/const' 30 import {ScrollbarOffsetContext} from '#/components/Layout/context' 31 import {Text} from '#/components/Typography' 32 33 export function Outer({ 34 children, ··· 91 style={[ 92 a.flex_1, 93 a.justify_center, 94 - isIOS && align === 'platform' && a.align_center, 95 {minHeight: HEADER_SLOT_SIZE}, 96 ]}> 97 <AlignmentContext.Provider value={align}> ··· 186 a.text_lg, 187 a.font_semi_bold, 188 a.leading_tight, 189 - isIOS && align === 'platform' && a.text_center, 190 gtMobile && a.text_xl, 191 style, 192 ]} ··· 205 style={[ 206 a.text_sm, 207 a.leading_snug, 208 - isIOS && align === 'platform' && a.text_center, 209 t.atoms.text_contrast_medium, 210 ]} 211 numberOfLines={2}>
··· 6 7 import {HITSLOP_30} from '#/lib/constants' 8 import {type NavigationProp} from '#/lib/routes/types' 9 import {useSetDrawerOpen} from '#/state/shell' 10 import { 11 atoms as a, ··· 28 } from '#/components/Layout/const' 29 import {ScrollbarOffsetContext} from '#/components/Layout/context' 30 import {Text} from '#/components/Typography' 31 + import {IS_IOS} from '#/env' 32 33 export function Outer({ 34 children, ··· 91 style={[ 92 a.flex_1, 93 a.justify_center, 94 + IS_IOS && align === 'platform' && a.align_center, 95 {minHeight: HEADER_SLOT_SIZE}, 96 ]}> 97 <AlignmentContext.Provider value={align}> ··· 186 a.text_lg, 187 a.font_semi_bold, 188 a.leading_tight, 189 + IS_IOS && align === 'platform' && a.text_center, 190 gtMobile && a.text_xl, 191 style, 192 ]} ··· 205 style={[ 206 a.text_sm, 207 a.leading_snug, 208 + IS_IOS && align === 'platform' && a.text_center, 209 t.atoms.text_contrast_medium, 210 ]} 211 numberOfLines={2}>
+4 -4
src/components/Layout/index.tsx
··· 11 } from 'react-native-reanimated' 12 import {useSafeAreaInsets} from 'react-native-safe-area-context' 13 14 - import {isWeb} from '#/platform/detection' 15 import {useShellLayout} from '#/state/shell/shell-layout' 16 import { 17 atoms as a, ··· 23 import {useDialogContext} from '#/components/Dialog' 24 import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const' 25 import {ScrollbarOffsetContext} from '#/components/Layout/context' 26 27 export * from '#/components/Layout/const' 28 export * as Header from '#/components/Layout/Header' ··· 43 const {top} = useSafeAreaInsets() 44 return ( 45 <> 46 - {isWeb && <WebCenterBorders />} 47 <View 48 style={[a.util_screen_outer, {paddingTop: noInsetTop ? 0 : top}, style]} 49 {...props} ··· 98 contentContainerStyle, 99 ]} 100 {...props}> 101 - {isWeb ? ( 102 <Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}> 103 {/* @ts-expect-error web only -esb */} 104 {children} ··· 145 ]} 146 keyboardShouldPersistTaps="handled" 147 {...props}> 148 - {isWeb ? <Center>{children}</Center> : children} 149 </KeyboardAwareScrollView> 150 ) 151 })
··· 11 } from 'react-native-reanimated' 12 import {useSafeAreaInsets} from 'react-native-safe-area-context' 13 14 import {useShellLayout} from '#/state/shell/shell-layout' 15 import { 16 atoms as a, ··· 22 import {useDialogContext} from '#/components/Dialog' 23 import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const' 24 import {ScrollbarOffsetContext} from '#/components/Layout/context' 25 + import {IS_WEB} from '#/env' 26 27 export * from '#/components/Layout/const' 28 export * as Header from '#/components/Layout/Header' ··· 43 const {top} = useSafeAreaInsets() 44 return ( 45 <> 46 + {IS_WEB && <WebCenterBorders />} 47 <View 48 style={[a.util_screen_outer, {paddingTop: noInsetTop ? 0 : top}, style]} 49 {...props} ··· 98 contentContainerStyle, 99 ]} 100 {...props}> 101 + {IS_WEB ? ( 102 <Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}> 103 {/* @ts-expect-error web only -esb */} 104 {children} ··· 145 ]} 146 keyboardShouldPersistTaps="handled" 147 {...props}> 148 + {IS_WEB ? <Center>{children}</Center> : children} 149 </KeyboardAwareScrollView> 150 ) 151 })
+11 -11
src/components/Link.tsx
··· 18 isExternalUrl, 19 linkRequiresWarning, 20 } from '#/lib/strings/url-helpers' 21 - import {isNative, isWeb} from '#/platform/detection' 22 import {useModalControls} from '#/state/modals' 23 import {useGoLinksEnabled} from '#/state/preferences' 24 import {atoms as a, flatten, type TextStyleProp, useTheme, web} from '#/alf' 25 import {Button, type ButtonProps} from '#/components/Button' 26 import {useInteractionState} from '#/components/hooks/useInteractionState' 27 import {Text, type TextProps} from '#/components/Typography' 28 import {router} from '#/routes' 29 import {useGlobalDialogsControlContext} from './dialogs/Context' 30 ··· 133 linkRequiresWarning(href, displayText), 134 ) 135 136 - if (isWeb) { 137 e.preventDefault() 138 } 139 ··· 166 ] 167 168 // does not apply to web's flat navigator 169 - if (isNative && screen !== 'NotFound') { 170 const state = navigation.getState() 171 // if screen is not in the current navigator, it means it's 172 // most likely a tab screen. note: state can be undefined ··· 251 (e: GestureResponderEvent) => { 252 const exitEarlyIfFalse = outerOnLongPress?.(e) 253 if (exitEarlyIfFalse === false) return 254 - return isNative && shareOnLongPress ? handleLongPress() : undefined 255 }, 256 [outerOnLongPress, handleLongPress, shareOnLongPress], 257 ) ··· 506 onPress, 507 ...props 508 }: Omit<InlineLinkProps, 'onLongPress'>) { 509 - return isWeb ? ( 510 <InlineLinkText {...props} to={to} onPress={onPress}> 511 {children} 512 </InlineLinkText> ··· 552 ): {onPress: Exclude<BaseLinkProps['onPress'], undefined>} { 553 return { 554 onPress(e: GestureResponderEvent) { 555 - if (!isWeb || !isModifiedClickEvent(e)) { 556 e.preventDefault() 557 onPressHandler(e) 558 return false ··· 566 * intends to deviate from default behavior. 567 */ 568 export function isClickEventWithMetaKey(e: GestureResponderEvent) { 569 - if (!isWeb) return false 570 const event = e as unknown as MouseEvent 571 return event.metaKey || event.altKey || event.ctrlKey || event.shiftKey 572 } ··· 575 * Determines if the web click target is anything other than `_self` 576 */ 577 export function isClickTargetExternal(e: GestureResponderEvent) { 578 - if (!isWeb) return false 579 const event = e as unknown as MouseEvent 580 const el = event.currentTarget as HTMLAnchorElement 581 return el && el.target && el.target !== '_self' ··· 587 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button} 588 */ 589 export function isModifiedClickEvent(e: GestureResponderEvent): boolean { 590 - if (!isWeb) return false 591 const event = e as unknown as MouseEvent 592 const isPrimaryButton = event.button === 0 593 return ( ··· 601 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button} 602 */ 603 export function shouldClickOpenNewTab(e: GestureResponderEvent) { 604 - if (!isWeb) return false 605 const event = e as unknown as MouseEvent 606 - const isMiddleClick = isWeb && event.button === 1 607 return isClickEventWithMetaKey(e) || isClickTargetExternal(e) || isMiddleClick 608 }
··· 18 isExternalUrl, 19 linkRequiresWarning, 20 } from '#/lib/strings/url-helpers' 21 import {useModalControls} from '#/state/modals' 22 import {useGoLinksEnabled} from '#/state/preferences' 23 import {atoms as a, flatten, type TextStyleProp, useTheme, web} from '#/alf' 24 import {Button, type ButtonProps} from '#/components/Button' 25 import {useInteractionState} from '#/components/hooks/useInteractionState' 26 import {Text, type TextProps} from '#/components/Typography' 27 + import {IS_NATIVE, IS_WEB} from '#/env' 28 import {router} from '#/routes' 29 import {useGlobalDialogsControlContext} from './dialogs/Context' 30 ··· 133 linkRequiresWarning(href, displayText), 134 ) 135 136 + if (IS_WEB) { 137 e.preventDefault() 138 } 139 ··· 166 ] 167 168 // does not apply to web's flat navigator 169 + if (IS_NATIVE && screen !== 'NotFound') { 170 const state = navigation.getState() 171 // if screen is not in the current navigator, it means it's 172 // most likely a tab screen. note: state can be undefined ··· 251 (e: GestureResponderEvent) => { 252 const exitEarlyIfFalse = outerOnLongPress?.(e) 253 if (exitEarlyIfFalse === false) return 254 + return IS_NATIVE && shareOnLongPress ? handleLongPress() : undefined 255 }, 256 [outerOnLongPress, handleLongPress, shareOnLongPress], 257 ) ··· 506 onPress, 507 ...props 508 }: Omit<InlineLinkProps, 'onLongPress'>) { 509 + return IS_WEB ? ( 510 <InlineLinkText {...props} to={to} onPress={onPress}> 511 {children} 512 </InlineLinkText> ··· 552 ): {onPress: Exclude<BaseLinkProps['onPress'], undefined>} { 553 return { 554 onPress(e: GestureResponderEvent) { 555 + if (!IS_WEB || !isModifiedClickEvent(e)) { 556 e.preventDefault() 557 onPressHandler(e) 558 return false ··· 566 * intends to deviate from default behavior. 567 */ 568 export function isClickEventWithMetaKey(e: GestureResponderEvent) { 569 + if (!IS_WEB) return false 570 const event = e as unknown as MouseEvent 571 return event.metaKey || event.altKey || event.ctrlKey || event.shiftKey 572 } ··· 575 * Determines if the web click target is anything other than `_self` 576 */ 577 export function isClickTargetExternal(e: GestureResponderEvent) { 578 + if (!IS_WEB) return false 579 const event = e as unknown as MouseEvent 580 const el = event.currentTarget as HTMLAnchorElement 581 return el && el.target && el.target !== '_self' ··· 587 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button} 588 */ 589 export function isModifiedClickEvent(e: GestureResponderEvent): boolean { 590 + if (!IS_WEB) return false 591 const event = e as unknown as MouseEvent 592 const isPrimaryButton = event.button === 0 593 return ( ··· 601 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button} 602 */ 603 export function shouldClickOpenNewTab(e: GestureResponderEvent) { 604 + if (!IS_WEB) return false 605 const event = e as unknown as MouseEvent 606 + const isMiddleClick = IS_WEB && event.button === 1 607 return isClickEventWithMetaKey(e) || isClickTargetExternal(e) || isMiddleClick 608 }
+2 -2
src/components/MediaInsetBorder.tsx
··· 1 import {StyleSheet} from 'react-native' 2 import type React from 'react' 3 4 - import {isHighDPI} from '#/lib/browser' 5 import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf' 6 import {Fill} from '#/components/Fill' 7 8 /** 9 * Applies and thin border within a bounding box. Used to contrast media from ··· 33 // while we generally use hairlineWidth (aka 1px), 34 // we make an exception here for high DPI screens 35 // as the 1px border is very noticeable -sfn 36 - web: isHighDPI ? 0.5 : StyleSheet.hairlineWidth, 37 }), 38 }, 39 opaque
··· 1 import {StyleSheet} from 'react-native' 2 import type React from 'react' 3 4 import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf' 5 import {Fill} from '#/components/Fill' 6 + import {IS_HIGH_DPI} from '#/env' 7 8 /** 9 * Applies and thin border within a bounding box. Used to contrast media from ··· 33 // while we generally use hairlineWidth (aka 1px), 34 // we make an exception here for high DPI screens 35 // as the 1px border is very noticeable -sfn 36 + web: IS_HIGH_DPI ? 0.5 : StyleSheet.hairlineWidth, 37 }), 38 }, 39 opaque
+5 -5
src/components/Menu/index.tsx
··· 10 import {useLingui} from '@lingui/react' 11 import flattenReactChildren from 'react-keyed-flatten-children' 12 13 - import {isAndroid, isIOS, isNative} from '#/platform/detection' 14 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 15 import {atoms as a, useTheme} from '#/alf' 16 import {Button, ButtonText} from '#/components/Button' ··· 31 type TriggerProps, 32 } from '#/components/Menu/types' 33 import {Text} from '#/components/Typography' 34 35 export { 36 type DialogControlProps as MenuControlProps, ··· 71 } = useInteractionState() 72 73 return children({ 74 - isNative: true, 75 control: context.control, 76 state: { 77 hovered: false, ··· 112 <Dialog.ScrollableInner label={_(msg`Menu`)}> 113 <View style={[a.gap_lg]}> 114 {children} 115 - {isNative && showCancel && <Cancel />} 116 </View> 117 </Dialog.ScrollableInner> 118 </Context.Provider> ··· 138 onFocus={onFocus} 139 onBlur={onBlur} 140 onPress={async e => { 141 - if (isAndroid) { 142 /** 143 * Below fix for iOS doesn't work for Android, this does. 144 */ 145 onPress?.(e) 146 context.control.close() 147 - } else if (isIOS) { 148 /** 149 * Fixes a subtle bug on iOS 150 * {@link https://github.com/bluesky-social/social-app/pull/5849/files#diff-de516ef5e7bd9840cd639213301df38cf03acfcad5bda85a1d63efd249ba79deL124-L127}
··· 10 import {useLingui} from '@lingui/react' 11 import flattenReactChildren from 'react-keyed-flatten-children' 12 13 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 14 import {atoms as a, useTheme} from '#/alf' 15 import {Button, ButtonText} from '#/components/Button' ··· 30 type TriggerProps, 31 } from '#/components/Menu/types' 32 import {Text} from '#/components/Typography' 33 + import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env' 34 35 export { 36 type DialogControlProps as MenuControlProps, ··· 71 } = useInteractionState() 72 73 return children({ 74 + IS_NATIVE: true, 75 control: context.control, 76 state: { 77 hovered: false, ··· 112 <Dialog.ScrollableInner label={_(msg`Menu`)}> 113 <View style={[a.gap_lg]}> 114 {children} 115 + {IS_NATIVE && showCancel && <Cancel />} 116 </View> 117 </Dialog.ScrollableInner> 118 </Context.Provider> ··· 138 onFocus={onFocus} 139 onBlur={onBlur} 140 onPress={async e => { 141 + if (IS_ANDROID) { 142 /** 143 * Below fix for iOS doesn't work for Android, this does. 144 */ 145 onPress?.(e) 146 context.control.close() 147 + } else if (IS_IOS) { 148 /** 149 * Fixes a subtle bug on iOS 150 * {@link https://github.com/bluesky-social/social-app/pull/5849/files#diff-de516ef5e7bd9840cd639213301df38cf03acfcad5bda85a1d63efd249ba79deL124-L127}
+1 -1
src/components/Menu/index.web.tsx
··· 139 <RadixTriggerPassThrough> 140 {props => 141 children({ 142 - isNative: false, 143 control, 144 state: { 145 hovered,
··· 139 <RadixTriggerPassThrough> 140 {props => 141 children({ 142 + IS_NATIVE: false, 143 control, 144 state: { 145 hovered,
+2 -2
src/components/Menu/types.ts
··· 43 } 44 export type TriggerChildProps = 45 | { 46 - isNative: true 47 control: Dialog.DialogOuterProps['control'] 48 state: { 49 /** ··· 73 } 74 } 75 | { 76 - isNative: false 77 control: Dialog.DialogOuterProps['control'] 78 state: { 79 hovered: boolean
··· 43 } 44 export type TriggerChildProps = 45 | { 46 + IS_NATIVE: true 47 control: Dialog.DialogOuterProps['control'] 48 state: { 49 /** ··· 73 } 74 } 75 | { 76 + IS_NATIVE: false 77 control: Dialog.DialogOuterProps['control'] 78 state: { 79 hovered: boolean
+2 -2
src/components/NewskieDialog.tsx
··· 8 import {HITSLOP_10} from '#/lib/constants' 9 import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 10 import {sanitizeDisplayName} from '#/lib/strings/display-names' 11 - import {isNative} from '#/platform/detection' 12 import {useModerationOpts} from '#/state/preferences/moderation-opts' 13 import {useSession} from '#/state/session' 14 import {atoms as a, useTheme, web} from '#/alf' ··· 18 import {Newskie} from '#/components/icons/Newskie' 19 import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' 20 import {Text} from '#/components/Typography' 21 22 export function NewskieDialog({ 23 profile, ··· 162 </StarterPackCard.Link> 163 ) : null} 164 165 - {isNative && ( 166 <Button 167 label={_(msg`Close`)} 168 color="secondary"
··· 8 import {HITSLOP_10} from '#/lib/constants' 9 import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 10 import {sanitizeDisplayName} from '#/lib/strings/display-names' 11 import {useModerationOpts} from '#/state/preferences/moderation-opts' 12 import {useSession} from '#/state/session' 13 import {atoms as a, useTheme, web} from '#/alf' ··· 17 import {Newskie} from '#/components/icons/Newskie' 18 import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' 19 import {Text} from '#/components/Typography' 20 + import {IS_NATIVE} from '#/env' 21 22 export function NewskieDialog({ 23 profile, ··· 162 </StarterPackCard.Link> 163 ) : null} 164 165 + {IS_NATIVE && ( 166 <Button 167 label={_(msg`Close`)} 168 color="secondary"
+3 -3
src/components/PolicyUpdateOverlay/Overlay.tsx
··· 7 import {LinearGradient} from 'expo-linear-gradient' 8 import {utils} from '@bsky.app/alf' 9 10 - import {isAndroid, isNative} from '#/platform/detection' 11 import {useA11y} from '#/state/a11y' 12 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 13 import {FocusScope} from '#/components/FocusScope' 14 import {LockScroll} from '#/components/LockScroll' 15 16 const GUTTER = 24 17 ··· 80 a.z_20, 81 a.align_center, 82 !gtPhone && [a.justify_end, {minHeight: frame.height}], 83 - isNative && [ 84 { 85 paddingBottom: Math.max(insets.bottom, a.p_2xl.padding), 86 }, ··· 109 110 <FocusScope> 111 <View 112 - accessible={isAndroid} 113 role="dialog" 114 aria-role="dialog" 115 aria-label={label}
··· 7 import {LinearGradient} from 'expo-linear-gradient' 8 import {utils} from '@bsky.app/alf' 9 10 import {useA11y} from '#/state/a11y' 11 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 12 import {FocusScope} from '#/components/FocusScope' 13 import {LockScroll} from '#/components/LockScroll' 14 + import {IS_ANDROID, IS_NATIVE} from '#/env' 15 16 const GUTTER = 24 17 ··· 80 a.z_20, 81 a.align_center, 82 !gtPhone && [a.justify_end, {minHeight: frame.height}], 83 + IS_NATIVE && [ 84 { 85 paddingBottom: Math.max(insets.bottom, a.p_2xl.padding), 86 }, ··· 109 110 <FocusScope> 111 <View 112 + accessible={IS_ANDROID} 113 role="dialog" 114 aria-role="dialog" 115 aria-label={label}
+2 -2
src/components/PolicyUpdateOverlay/index.tsx
··· 1 import {useEffect} from 'react' 2 import {View} from 'react-native' 3 4 - import {isIOS} from '#/platform/detection' 5 import {atoms as a} from '#/alf' 6 import {FullWindowOverlay} from '#/components/FullWindowOverlay' 7 import {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' 8 import {Portal} from '#/components/PolicyUpdateOverlay/Portal' 9 import {Content} from '#/components/PolicyUpdateOverlay/updates/202508' 10 11 export {Provider} from '#/components/PolicyUpdateOverlay/context' 12 export {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' ··· 39 // setting a zIndex when using FullWindowOverlay on iOS 40 // means the taps pass straight through to the underlying content (???) 41 // so don't set it on iOS. FullWindowOverlay already does the job. 42 - !isIOS && {zIndex: 9999}, 43 ]}> 44 <Content state={state} /> 45 </View>
··· 1 import {useEffect} from 'react' 2 import {View} from 'react-native' 3 4 import {atoms as a} from '#/alf' 5 import {FullWindowOverlay} from '#/components/FullWindowOverlay' 6 import {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' 7 import {Portal} from '#/components/PolicyUpdateOverlay/Portal' 8 import {Content} from '#/components/PolicyUpdateOverlay/updates/202508' 9 + import {IS_IOS} from '#/env' 10 11 export {Provider} from '#/components/PolicyUpdateOverlay/context' 12 export {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' ··· 39 // setting a zIndex when using FullWindowOverlay on iOS 40 // means the taps pass straight through to the underlying content (???) 41 // so don't set it on iOS. FullWindowOverlay already does the job. 42 + !IS_IOS && {zIndex: 9999}, 43 ]}> 44 <Content state={state} /> 45 </View>
+2 -2
src/components/PolicyUpdateOverlay/updates/202508/index.tsx
··· 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 - import {isAndroid} from '#/platform/detection' 7 import {useA11y} from '#/state/a11y' 8 import {atoms as a, useTheme} from '#/alf' 9 import {Button, ButtonText} from '#/components/Button' ··· 12 import {Overlay} from '#/components/PolicyUpdateOverlay/Overlay' 13 import {type PolicyUpdateState} from '#/components/PolicyUpdateOverlay/usePolicyUpdateState' 14 import {Text} from '#/components/Typography' 15 16 export function Content({state}: {state: PolicyUpdateState}) { 17 const t = useTheme() ··· 56 size: 'small', 57 } as const 58 59 - const label = isAndroid 60 ? _( 61 msg`We’re updating our Terms of Service, Privacy Policy, and Copyright Policy, effective September 15th, 2025. We're also updating our Community Guidelines, and we want your input! These new guidelines will take effect on October 15th, 2025. Learn more about these changes and how to share your thoughts with us by reading our blog post.`, 62 )
··· 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import {useA11y} from '#/state/a11y' 7 import {atoms as a, useTheme} from '#/alf' 8 import {Button, ButtonText} from '#/components/Button' ··· 11 import {Overlay} from '#/components/PolicyUpdateOverlay/Overlay' 12 import {type PolicyUpdateState} from '#/components/PolicyUpdateOverlay/usePolicyUpdateState' 13 import {Text} from '#/components/Typography' 14 + import {IS_ANDROID} from '#/env' 15 16 export function Content({state}: {state: PolicyUpdateState}) { 17 const t = useTheme() ··· 56 size: 'small', 57 } as const 58 59 + const label = IS_ANDROID 60 ? _( 61 msg`We’re updating our Terms of Service, Privacy Policy, and Copyright Policy, effective September 15th, 2025. We're also updating our Community Guidelines, and we want your input! These new guidelines will take effect on October 15th, 2025. Learn more about these changes and how to share your thoughts with us by reading our blog post.`, 62 )
+5 -5
src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx
··· 10 import {useLingui} from '@lingui/react' 11 12 import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 13 - import {isIOS, isNative, isWeb} from '#/platform/detection' 14 import {useExternalEmbedsPrefs} from '#/state/preferences' 15 import {atoms as a, useTheme} from '#/alf' 16 import {useDialogControl} from '#/components/Dialog' 17 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' 18 import {Fill} from '#/components/Fill' 19 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 20 21 export function ExternalGif({ 22 link, ··· 66 // Control animation on native 67 setIsAnimating(prev => { 68 if (prev) { 69 - if (isNative) { 70 imageRef.current?.stopAnimating() 71 } 72 return false 73 } else { 74 - if (isNative) { 75 imageRef.current?.startAnimating() 76 } 77 return true ··· 112 <Image 113 source={{ 114 uri: 115 - !isPrefetched || (isWeb && !isAnimating) 116 ? link.thumb 117 : params.playerUri, 118 }} // Web uses the thumb to control playback ··· 123 accessibilityIgnoresInvertColors 124 accessibilityLabel={link.title} 125 accessibilityHint={link.title} 126 - cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios 127 /> 128 129 {(!isPrefetched || !isAnimating) && (
··· 10 import {useLingui} from '@lingui/react' 11 12 import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 13 import {useExternalEmbedsPrefs} from '#/state/preferences' 14 import {atoms as a, useTheme} from '#/alf' 15 import {useDialogControl} from '#/components/Dialog' 16 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' 17 import {Fill} from '#/components/Fill' 18 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 19 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 20 21 export function ExternalGif({ 22 link, ··· 66 // Control animation on native 67 setIsAnimating(prev => { 68 if (prev) { 69 + if (IS_NATIVE) { 70 imageRef.current?.stopAnimating() 71 } 72 return false 73 } else { 74 + if (IS_NATIVE) { 75 imageRef.current?.startAnimating() 76 } 77 return true ··· 112 <Image 113 source={{ 114 uri: 115 + !isPrefetched || (IS_WEB && !isAnimating) 116 ? link.thumb 117 : params.playerUri, 118 }} // Web uses the thumb to control playback ··· 123 accessibilityIgnoresInvertColors 124 accessibilityLabel={link.title} 125 accessibilityHint={link.title} 126 + cachePolicy={IS_IOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios 127 /> 128 129 {(!isPrefetched || !isAnimating) && (
+2 -2
src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx
··· 26 type EmbedPlayerParams, 27 getPlayerAspect, 28 } from '#/lib/strings/embed-player' 29 - import {isNative} from '#/platform/detection' 30 import {useExternalEmbedsPrefs} from '#/state/preferences' 31 import {EventStopper} from '#/view/com/util/EventStopper' 32 import {atoms as a, useTheme} from '#/alf' ··· 34 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' 35 import {Fill} from '#/components/Fill' 36 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 37 38 interface ShouldStartLoadRequest { 39 url: string ··· 148 const {height: winHeight, width: winWidth} = windowDims 149 150 // Get the proper screen height depending on what is going on 151 - const realWinHeight = isNative // If it is native, we always want the larger number 152 ? winHeight > winWidth 153 ? winHeight 154 : winWidth
··· 26 type EmbedPlayerParams, 27 getPlayerAspect, 28 } from '#/lib/strings/embed-player' 29 import {useExternalEmbedsPrefs} from '#/state/preferences' 30 import {EventStopper} from '#/view/com/util/EventStopper' 31 import {atoms as a, useTheme} from '#/alf' ··· 33 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' 34 import {Fill} from '#/components/Fill' 35 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 36 + import {IS_NATIVE} from '#/env' 37 38 interface ShouldStartLoadRequest { 39 url: string ··· 148 const {height: winHeight, width: winWidth} = windowDims 149 150 // Get the proper screen height depending on what is going on 151 + const realWinHeight = IS_NATIVE // If it is native, we always want the larger number 152 ? winHeight > winWidth 153 ? winHeight 154 : winWidth
+6 -6
src/components/Post/Embed/ExternalEmbed/Gif.tsx
··· 13 import {HITSLOP_20} from '#/lib/constants' 14 import {clamp} from '#/lib/numbers' 15 import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 16 - import {isWeb} from '#/platform/detection' 17 import {useAutoplayDisabled} from '#/state/preferences' 18 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' 19 import {atoms as a, useTheme} from '#/alf' ··· 22 import * as Prompt from '#/components/Prompt' 23 import {Text} from '#/components/Typography' 24 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 25 import {GifView} from '../../../../../modules/expo-bluesky-gif-view' 26 import {type GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types' 27 ··· 218 altContainer: { 219 backgroundColor: 'rgba(0, 0, 0, 0.75)', 220 borderRadius: 6, 221 - paddingHorizontal: isWeb ? 8 : 6, 222 - paddingVertical: isWeb ? 6 : 3, 223 position: 'absolute', 224 // Related to margin/gap hack. This keeps the alt label in the same position 225 // on all platforms 226 - right: isWeb ? 8 : 5, 227 - bottom: isWeb ? 8 : 5, 228 zIndex: 2, 229 }, 230 alt: { 231 color: 'white', 232 - fontSize: isWeb ? 10 : 7, 233 fontWeight: '600', 234 }, 235 })
··· 13 import {HITSLOP_20} from '#/lib/constants' 14 import {clamp} from '#/lib/numbers' 15 import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 16 import {useAutoplayDisabled} from '#/state/preferences' 17 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' 18 import {atoms as a, useTheme} from '#/alf' ··· 21 import * as Prompt from '#/components/Prompt' 22 import {Text} from '#/components/Typography' 23 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 24 + import {IS_WEB} from '#/env' 25 import {GifView} from '../../../../../modules/expo-bluesky-gif-view' 26 import {type GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types' 27 ··· 218 altContainer: { 219 backgroundColor: 'rgba(0, 0, 0, 0.75)', 220 borderRadius: 6, 221 + paddingHorizontal: IS_WEB ? 8 : 6, 222 + paddingVertical: IS_WEB ? 6 : 3, 223 position: 'absolute', 224 // Related to margin/gap hack. This keeps the alt label in the same position 225 // on all platforms 226 + right: IS_WEB ? 8 : 5, 227 + bottom: IS_WEB ? 8 : 5, 228 zIndex: 2, 229 }, 230 alt: { 231 color: 'white', 232 + fontSize: IS_WEB ? 10 : 7, 233 fontWeight: '600', 234 }, 235 })
+2 -2
src/components/Post/Embed/ExternalEmbed/index.tsx
··· 10 import {shareUrl} from '#/lib/sharing' 11 import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' 12 import {toNiceDomain} from '#/lib/strings/url-helpers' 13 - import {isNative} from '#/platform/detection' 14 import {useExternalEmbedsPrefs} from '#/state/preferences' 15 import {atoms as a, useTheme} from '#/alf' 16 import {Divider} from '#/components/Divider' 17 import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' 18 import {Link} from '#/components/Link' 19 import {Text} from '#/components/Typography' 20 import {ExternalGif} from './ExternalGif' 21 import {ExternalPlayer} from './ExternalPlayer' 22 import {GifEmbed} from './Gif' ··· 53 }, [playHaptic, onOpen]) 54 55 const onShareExternal = useCallback(() => { 56 - if (link.uri && isNative) { 57 playHaptic('Heavy') 58 shareUrl(link.uri) 59 }
··· 10 import {shareUrl} from '#/lib/sharing' 11 import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' 12 import {toNiceDomain} from '#/lib/strings/url-helpers' 13 import {useExternalEmbedsPrefs} from '#/state/preferences' 14 import {atoms as a, useTheme} from '#/alf' 15 import {Divider} from '#/components/Divider' 16 import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' 17 import {Link} from '#/components/Link' 18 import {Text} from '#/components/Typography' 19 + import {IS_NATIVE} from '#/env' 20 import {ExternalGif} from './ExternalGif' 21 import {ExternalPlayer} from './ExternalPlayer' 22 import {GifEmbed} from './Gif' ··· 53 }, [playHaptic, onOpen]) 54 55 const onShareExternal = useCallback(() => { 56 + if (link.uri && IS_NATIVE) { 57 playHaptic('Heavy') 58 shareUrl(link.uri) 59 }
+3 -3
src/components/Post/Embed/VideoEmbed/ActiveVideoWebContext.tsx
··· 8 } from 'react' 9 import {useWindowDimensions} from 'react-native' 10 11 - import {isNative, isWeb} from '#/platform/detection' 12 13 const Context = React.createContext<{ 14 activeViewId: string | null ··· 18 Context.displayName = 'ActiveVideoWebContext' 19 20 export function Provider({children}: {children: React.ReactNode}) { 21 - if (!isWeb) { 22 throw new Error('ActiveVideoWebContext may only be used on web.') 23 } 24 ··· 47 48 const sendViewPosition = useCallback( 49 (viewId: string, y: number) => { 50 - if (isNative) return 51 52 if (viewId === activeViewIdRef.current) { 53 activeViewLocationRef.current = y
··· 8 } from 'react' 9 import {useWindowDimensions} from 'react-native' 10 11 + import {IS_NATIVE, IS_WEB} from '#/env' 12 13 const Context = React.createContext<{ 14 activeViewId: string | null ··· 18 Context.displayName = 'ActiveVideoWebContext' 19 20 export function Provider({children}: {children: React.ReactNode}) { 21 + if (!IS_WEB) { 22 throw new Error('ActiveVideoWebContext may only be used on web.') 23 } 24 ··· 47 48 const sendViewPosition = useCallback( 49 (viewId: string, y: number) => { 50 + if (IS_NATIVE) return 51 52 if (viewId === activeViewIdRef.current) { 53 activeViewLocationRef.current = y
+3 -3
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
··· 3 import {msg} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 - import {isFirefox, isTouchDevice} from '#/lib/browser' 7 import {clamp} from '#/lib/numbers' 8 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {useInteractionState} from '#/components/hooks/useInteractionState' 11 import {formatTime} from './utils' 12 13 export function Scrubber({ ··· 102 // a pointerUp event is fired outside the element that captured the 103 // pointer. Firefox clicks on the element the mouse is over, so we have 104 // to make everything unclickable while seeking -sfn 105 - if (isFirefox && scrubberActive) { 106 document.body.classList.add('force-no-clicks') 107 108 return () => { ··· 153 <View 154 testID="scrubber" 155 style={[ 156 - {height: isTouchDevice ? 32 : 18, width: '100%'}, 157 a.flex_shrink_0, 158 a.px_xs, 159 ]}
··· 3 import {msg} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import {clamp} from '#/lib/numbers' 7 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 8 import {atoms as a, useTheme, web} from '#/alf' 9 import {useInteractionState} from '#/components/hooks/useInteractionState' 10 + import {IS_WEB_FIREFOX, IS_WEB_TOUCH_DEVICE} from '#/env' 11 import {formatTime} from './utils' 12 13 export function Scrubber({ ··· 102 // a pointerUp event is fired outside the element that captured the 103 // pointer. Firefox clicks on the element the mouse is over, so we have 104 // to make everything unclickable while seeking -sfn 105 + if (IS_WEB_FIREFOX && scrubberActive) { 106 document.body.classList.add('force-no-clicks') 107 108 return () => { ··· 153 <View 154 testID="scrubber" 155 style={[ 156 + {height: IS_WEB_TOUCH_DEVICE ? 32 : 18, width: '100%'}, 157 a.flex_shrink_0, 158 a.px_xs, 159 ]}
+7 -8
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
··· 4 import {useLingui} from '@lingui/react' 5 import type Hls from 'hls.js' 6 7 - import {isTouchDevice} from '#/lib/browser' 8 import {clamp} from '#/lib/numbers' 9 - import {isIPhoneWeb} from '#/platform/detection' 10 import { 11 useAutoplayDisabled, 12 useSetSubtitlesEnabled, ··· 28 import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' 29 import {Loader} from '#/components/Loader' 30 import {Text} from '#/components/Typography' 31 import {TimeIndicator} from '../TimeIndicator' 32 import {ControlButton} from './ControlButton' 33 import {Scrubber} from './Scrubber' ··· 215 216 const seekLeft = useCallback(() => { 217 if (!videoRef.current) return 218 - // eslint-disable-next-line @typescript-eslint/no-shadow 219 const currentTime = videoRef.current.currentTime 220 - // eslint-disable-next-line @typescript-eslint/no-shadow 221 const duration = videoRef.current.duration || 0 222 onSeek(clamp(currentTime - 5, 0, duration)) 223 }, [onSeek, videoRef]) 224 225 const seekRight = useCallback(() => { 226 if (!videoRef.current) return 227 - // eslint-disable-next-line @typescript-eslint/no-shadow 228 const currentTime = videoRef.current.currentTime 229 - // eslint-disable-next-line @typescript-eslint/no-shadow 230 const duration = videoRef.current.duration || 0 231 onSeek(clamp(currentTime + 5, 0, duration)) 232 }, [onSeek, videoRef]) ··· 342 {opacity: showControls ? 1 : 0}, 343 {transition: 'opacity 0.2s ease-in-out'}, 344 ]}> 345 - {(!volumeHovered || isTouchDevice) && ( 346 <Scrubber 347 duration={duration} 348 currentTime={currentTime} ··· 400 onEndHover={onVolumeEndHover} 401 drawFocus={drawFocus} 402 /> 403 - {!isIPhoneWeb && ( 404 <ControlButton 405 active={isFullscreen} 406 activeLabel={_(msg`Exit fullscreen`)}
··· 4 import {useLingui} from '@lingui/react' 5 import type Hls from 'hls.js' 6 7 import {clamp} from '#/lib/numbers' 8 import { 9 useAutoplayDisabled, 10 useSetSubtitlesEnabled, ··· 26 import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' 27 import {Loader} from '#/components/Loader' 28 import {Text} from '#/components/Typography' 29 + import {IS_WEB_MOBILE_IOS, IS_WEB_TOUCH_DEVICE} from '#/env' 30 import {TimeIndicator} from '../TimeIndicator' 31 import {ControlButton} from './ControlButton' 32 import {Scrubber} from './Scrubber' ··· 214 215 const seekLeft = useCallback(() => { 216 if (!videoRef.current) return 217 + 218 const currentTime = videoRef.current.currentTime 219 + 220 const duration = videoRef.current.duration || 0 221 onSeek(clamp(currentTime - 5, 0, duration)) 222 }, [onSeek, videoRef]) 223 224 const seekRight = useCallback(() => { 225 if (!videoRef.current) return 226 + 227 const currentTime = videoRef.current.currentTime 228 + 229 const duration = videoRef.current.duration || 0 230 onSeek(clamp(currentTime + 5, 0, duration)) 231 }, [onSeek, videoRef]) ··· 341 {opacity: showControls ? 1 : 0}, 342 {transition: 'opacity 0.2s ease-in-out'}, 343 ]}> 344 + {(!volumeHovered || IS_WEB_TOUCH_DEVICE) && ( 345 <Scrubber 346 duration={duration} 347 currentTime={currentTime} ··· 399 onEndHover={onVolumeEndHover} 400 drawFocus={drawFocus} 401 /> 402 + {!IS_WEB_MOBILE_IOS && ( 403 <ControlButton 404 active={isFullscreen} 405 activeLabel={_(msg`Exit fullscreen`)}
+5 -3
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
··· 4 import {msg} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 - import {isSafari, isTouchDevice} from '#/lib/browser' 8 import {atoms as a} from '#/alf' 9 import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 10 import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 11 import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 12 import {ControlButton} from './ControlButton' 13 14 export function VolumeControl({ ··· 57 onPointerEnter={onHover} 58 onPointerLeave={onEndHover} 59 style={[a.relative]}> 60 - {hovered && !isTouchDevice && ( 61 <Animated.View 62 entering={FadeIn.duration(100)} 63 exiting={FadeOut.duration(100)} ··· 80 aria-label={_(msg`Volume`)} 81 style={ 82 // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h 83 - isSafari ? {height: 92, minHeight: '100%'} : {height: '100%'} 84 } 85 onChange={onVolumeChange} 86 // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
··· 4 import {msg} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 import {atoms as a} from '#/alf' 8 import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 9 import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 10 import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 11 + import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env' 12 import {ControlButton} from './ControlButton' 13 14 export function VolumeControl({ ··· 57 onPointerEnter={onHover} 58 onPointerLeave={onEndHover} 59 style={[a.relative]}> 60 + {hovered && !IS_WEB_TOUCH_DEVICE && ( 61 <Animated.View 62 entering={FadeIn.duration(100)} 63 exiting={FadeOut.duration(100)} ··· 80 aria-label={_(msg`Volume`)} 81 style={ 82 // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h 83 + IS_WEB_SAFARI 84 + ? {height: 92, minHeight: '100%'} 85 + : {height: '100%'} 86 } 87 onChange={onVolumeChange} 88 // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
+2 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx
··· 1 import {type RefObject, useCallback, useEffect, useRef, useState} from 'react' 2 3 - import {isSafari} from '#/lib/browser' 4 import {logger} from '#/logger' 5 import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 6 7 export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) { 8 const [playing, setPlaying] = useState(false) ··· 41 setCurrentTime(round(ref.current.currentTime) || 0) 42 // HACK: Safari randomly fires `stalled` events when changing between segments 43 // let's just clear the buffering state if the video is still progressing -sfn 44 - if (isSafari) { 45 if (bufferingTimeout) clearTimeout(bufferingTimeout) 46 setBuffering(false) 47 }
··· 1 import {type RefObject, useCallback, useEffect, useRef, useState} from 'react' 2 3 import {logger} from '#/logger' 4 import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 5 + import {IS_WEB_SAFARI} from '#/env' 6 7 export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) { 8 const [playing, setPlaying] = useState(false) ··· 41 setCurrentTime(round(ref.current.currentTime) || 0) 42 // HACK: Safari randomly fires `stalled` events when changing between segments 43 // let's just clear the buffering state if the video is still progressing -sfn 44 + if (IS_WEB_SAFARI) { 45 if (bufferingTimeout) clearTimeout(bufferingTimeout) 46 setBuffering(false) 47 }
+3 -3
src/components/Post/Embed/VideoEmbed/index.web.tsx
··· 11 import {msg} from '@lingui/macro' 12 import {useLingui} from '@lingui/react' 13 14 - import {isFirefox} from '#/lib/browser' 15 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 16 import {atoms as a, useTheme} from '#/alf' 17 import {useIsWithinMessage} from '#/components/dms/MessageContext' ··· 23 VideoEmbedInnerWeb, 24 VideoNotFoundError, 25 } from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' 26 import {useActiveVideoWeb} from './ActiveVideoWebContext' 27 import * as VideoFallback from './VideoEmbedInner/VideoFallback' 28 ··· 37 38 useEffect(() => { 39 if (!ref.current) return 40 - if (isFullscreen && !isFirefox) return 41 const observer = new IntersectionObserver( 42 entries => { 43 const entry = entries[0] ··· 150 // observing a div of 100vh height 151 useEffect(() => { 152 if (!ref.current) return 153 - if (isFullscreen && !isFirefox) return 154 const observer = new IntersectionObserver( 155 entries => { 156 const entry = entries[0]
··· 11 import {msg} from '@lingui/macro' 12 import {useLingui} from '@lingui/react' 13 14 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 15 import {atoms as a, useTheme} from '#/alf' 16 import {useIsWithinMessage} from '#/components/dms/MessageContext' ··· 22 VideoEmbedInnerWeb, 23 VideoNotFoundError, 24 } from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' 25 + import {IS_WEB_FIREFOX} from '#/env' 26 import {useActiveVideoWeb} from './ActiveVideoWebContext' 27 import * as VideoFallback from './VideoEmbedInner/VideoFallback' 28 ··· 37 38 useEffect(() => { 39 if (!ref.current) return 40 + if (isFullscreen && !IS_WEB_FIREFOX) return 41 const observer = new IntersectionObserver( 42 entries => { 43 const entry = entries[0] ··· 150 // observing a div of 100vh height 151 useEffect(() => { 152 if (!ref.current) return 153 + if (isFullscreen && !IS_WEB_FIREFOX) return 154 const observer = new IntersectionObserver( 155 entries => { 156 const entry = entries[0]
+3 -4
src/components/PostControls/PostMenu/PostMenuItems.tsx
··· 41 import {restoreLinks} from '#/lib/strings/rich-text-manip' 42 import {toShareUrl} from '#/lib/strings/url-helpers' 43 import {logger} from '#/logger' 44 - import {isWeb} from '#/platform/detection' 45 import {type Shadow} from '#/state/cache/post-shadow' 46 import {useProfileShadow} from '#/state/cache/profile-shadow' 47 import {useFeedFeedbackContext} from '#/state/feed-feedback' ··· 102 useReportDialogControl, 103 } from '#/components/moderation/ReportDialog' 104 import * as Prompt from '#/components/Prompt' 105 - import {IS_INTERNAL} from '#/env' 106 import * as bsky from '#/types/bsky' 107 108 let PostMenuItems = ({ ··· 611 Toast.show(_(msg({message: 'Downloading video...', context: 'toast'}))) 612 613 let success 614 - if (isWeb) success = await downloadVideoWeb({uri: uri}) 615 else success = await saveVideoToMediaLibrary({uri: uri}) 616 617 if (success) Toast.show('Video downloaded', 'check') ··· 624 Toast.show(_(msg({message: 'Downloading GIF...', context: 'toast'}))) 625 626 let success 627 - if (isWeb) success = await downloadVideoWeb({uri: gifEmbed.external.uri}) 628 else success = await saveVideoToMediaLibrary({uri: gifEmbed.external.uri}) 629 630 if (success) Toast.show('GIF downloaded', 'check')
··· 41 import {restoreLinks} from '#/lib/strings/rich-text-manip' 42 import {toShareUrl} from '#/lib/strings/url-helpers' 43 import {logger} from '#/logger' 44 import {type Shadow} from '#/state/cache/post-shadow' 45 import {useProfileShadow} from '#/state/cache/profile-shadow' 46 import {useFeedFeedbackContext} from '#/state/feed-feedback' ··· 101 useReportDialogControl, 102 } from '#/components/moderation/ReportDialog' 103 import * as Prompt from '#/components/Prompt' 104 + import {IS_INTERNAL, IS_WEB} from '#/env' 105 import * as bsky from '#/types/bsky' 106 107 let PostMenuItems = ({ ··· 610 Toast.show(_(msg({message: 'Downloading video...', context: 'toast'}))) 611 612 let success 613 + if (IS_WEB) success = await downloadVideoWeb({uri: uri}) 614 else success = await saveVideoToMediaLibrary({uri: uri}) 615 616 if (success) Toast.show('Video downloaded', 'check') ··· 623 Toast.show(_(msg({message: 'Downloading GIF...', context: 'toast'}))) 624 625 let success 626 + if (IS_WEB) success = await downloadVideoWeb({uri: gifEmbed.external.uri}) 627 else success = await saveVideoToMediaLibrary({uri: gifEmbed.external.uri}) 628 629 if (success) Toast.show('GIF downloaded', 'check')
+2 -2
src/components/PostControls/ShareMenu/ShareMenuItems.tsx
··· 11 import {shareText, shareUrl} from '#/lib/sharing' 12 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 13 import {logger} from '#/logger' 14 - import {isIOS} from '#/platform/detection' 15 import {useProfileShadow} from '#/state/cache/profile-shadow' 16 import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 17 import {useSession} from '#/state/session' ··· 27 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' 28 import * as Menu from '#/components/Menu' 29 import {useAgeAssurance} from '#/ageAssurance' 30 import {useDevMode} from '#/storage/hooks/dev-mode' 31 import {RecentChats} from './RecentChats' 32 import {type ShareMenuItemsProps} from './ShareMenuItems.types' ··· 74 const onCopyLink = async () => { 75 logger.metric('share:press:copyLink', {}, {statsig: true}) 76 const url = toShareUrl(href) 77 - if (isIOS) { 78 // iOS only 79 await ExpoClipboard.setUrlAsync(url) 80 } else {
··· 11 import {shareText, shareUrl} from '#/lib/sharing' 12 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 13 import {logger} from '#/logger' 14 import {useProfileShadow} from '#/state/cache/profile-shadow' 15 import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 16 import {useSession} from '#/state/session' ··· 26 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' 27 import * as Menu from '#/components/Menu' 28 import {useAgeAssurance} from '#/ageAssurance' 29 + import {IS_IOS} from '#/env' 30 import {useDevMode} from '#/storage/hooks/dev-mode' 31 import {RecentChats} from './RecentChats' 32 import {type ShareMenuItemsProps} from './ShareMenuItems.types' ··· 74 const onCopyLink = async () => { 75 logger.metric('share:press:copyLink', {}, {statsig: true}) 76 const url = toShareUrl(href) 77 + if (IS_IOS) { 78 // iOS only 79 await ExpoClipboard.setUrlAsync(url) 80 } else {
+2 -2
src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
··· 10 import {shareText, shareUrl} from '#/lib/sharing' 11 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 12 import {logger} from '#/logger' 13 - import {isWeb} from '#/platform/detection' 14 import {useProfileShadow} from '#/state/cache/profile-shadow' 15 import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 16 import {useSession} from '#/state/session' ··· 25 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' 26 import * as Menu from '#/components/Menu' 27 import {useAgeAssurance} from '#/ageAssurance' 28 import {useDevMode} from '#/storage/hooks/dev-mode' 29 import {type ShareMenuItemsProps} from './ShareMenuItems.types' 30 ··· 81 }) 82 } 83 84 - const canEmbed = isWeb && gtMobile && !hideInPWI 85 86 const onShareATURI = () => { 87 shareText(postUri)
··· 10 import {shareText, shareUrl} from '#/lib/sharing' 11 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 12 import {logger} from '#/logger' 13 import {useProfileShadow} from '#/state/cache/profile-shadow' 14 import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 15 import {useSession} from '#/state/session' ··· 24 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' 25 import * as Menu from '#/components/Menu' 26 import {useAgeAssurance} from '#/ageAssurance' 27 + import {IS_WEB} from '#/env' 28 import {useDevMode} from '#/storage/hooks/dev-mode' 29 import {type ShareMenuItemsProps} from './ShareMenuItems.types' 30 ··· 81 }) 82 } 83 84 + const canEmbed = IS_WEB && gtMobile && !hideInPWI 85 86 const onShareATURI = () => { 87 shareText(postUri)
+2 -2
src/components/ProfileHoverCard/index.web.tsx
··· 11 import {useNavigation} from '@react-navigation/native' 12 13 import {useActorStatus} from '#/lib/actor-status' 14 - import {isTouchDevice} from '#/lib/browser' 15 import {getModerationCauseKey} from '#/lib/moderation' 16 import {makeProfileLink} from '#/lib/routes/links' 17 import {type NavigationProp} from '#/lib/routes/types' ··· 47 import {Text} from '#/components/Typography' 48 import {useSimpleVerificationState} from '#/components/verification' 49 import {VerificationCheck} from '#/components/verification/VerificationCheck' 50 import {type ProfileHoverCardProps} from './types' 51 52 const floatingMiddlewares = [ ··· 74 } 75 } 76 77 - if (props.disable || isTouchDevice) { 78 return props.children 79 } else { 80 return (
··· 11 import {useNavigation} from '@react-navigation/native' 12 13 import {useActorStatus} from '#/lib/actor-status' 14 import {getModerationCauseKey} from '#/lib/moderation' 15 import {makeProfileLink} from '#/lib/routes/links' 16 import {type NavigationProp} from '#/lib/routes/types' ··· 46 import {Text} from '#/components/Typography' 47 import {useSimpleVerificationState} from '#/components/verification' 48 import {VerificationCheck} from '#/components/verification/VerificationCheck' 49 + import {IS_WEB_TOUCH_DEVICE} from '#/env' 50 import {type ProfileHoverCardProps} from './types' 51 52 const floatingMiddlewares = [ ··· 74 } 75 } 76 77 + if (props.disable || IS_WEB_TOUCH_DEVICE) { 78 return props.children 79 } else { 80 return (
+5 -5
src/components/ProgressGuide/FollowDialog.tsx
··· 12 import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' 13 import {logEvent} from '#/lib/statsig/statsig' 14 import {logger} from '#/logger' 15 - import {isWeb} from '#/platform/detection' 16 import {useModerationOpts} from '#/state/preferences/moderation-opts' 17 import {useActorSearch} from '#/state/queries/actor-search' 18 import {usePreferencesQuery} from '#/state/queries/preferences' ··· 37 import {boostInterests, InterestTabs} from '#/components/InterestTabs' 38 import * as ProfileCard from '#/components/ProfileCard' 39 import {Text} from '#/components/Typography' 40 import type * as bsky from '#/types/bsky' 41 import {ProgressGuideTask} from './Task' 42 ··· 431 <Trans>Find people to follow</Trans> 432 </Text> 433 {guide && ( 434 - <View style={isWeb && {paddingRight: 36}}> 435 <ProgressGuideTask 436 current={guide.numFollows + 1} 437 total={10 + 1} ··· 440 /> 441 </View> 442 )} 443 - {isWeb ? ( 444 <Button 445 label={_(msg`Close`)} 446 size="small" 447 shape="round" 448 - variant={isWeb ? 'ghost' : 'solid'} 449 color="secondary" 450 style={[ 451 a.absolute, ··· 579 <ProfileCard.Outer> 580 <ProfileCard.Header> 581 <ProfileCard.Avatar 582 - disabledPreview={!isWeb} 583 profile={profile} 584 moderationOpts={moderationOpts} 585 />
··· 12 import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' 13 import {logEvent} from '#/lib/statsig/statsig' 14 import {logger} from '#/logger' 15 import {useModerationOpts} from '#/state/preferences/moderation-opts' 16 import {useActorSearch} from '#/state/queries/actor-search' 17 import {usePreferencesQuery} from '#/state/queries/preferences' ··· 36 import {boostInterests, InterestTabs} from '#/components/InterestTabs' 37 import * as ProfileCard from '#/components/ProfileCard' 38 import {Text} from '#/components/Typography' 39 + import {IS_WEB} from '#/env' 40 import type * as bsky from '#/types/bsky' 41 import {ProgressGuideTask} from './Task' 42 ··· 431 <Trans>Find people to follow</Trans> 432 </Text> 433 {guide && ( 434 + <View style={IS_WEB && {paddingRight: 36}}> 435 <ProgressGuideTask 436 current={guide.numFollows + 1} 437 total={10 + 1} ··· 440 /> 441 </View> 442 )} 443 + {IS_WEB ? ( 444 <Button 445 label={_(msg`Close`)} 446 size="small" 447 shape="round" 448 + variant={IS_WEB ? 'ghost' : 'solid'} 449 color="secondary" 450 style={[ 451 a.absolute, ··· 579 <ProfileCard.Outer> 580 <ProfileCard.Header> 581 <ProfileCard.Avatar 582 + disabledPreview={!IS_WEB} 583 profile={profile} 584 moderationOpts={moderationOpts} 585 />
+3 -3
src/components/ProgressGuide/Toast.tsx
··· 11 import {msg} from '@lingui/macro' 12 import {useLingui} from '@lingui/react' 13 14 - import {isWeb} from '#/platform/detection' 15 import {atoms as a, useTheme} from '#/alf' 16 import {Portal} from '#/components/Portal' 17 import {AnimatedCheck, type AnimatedCheckRef} from '../anim/AnimatedCheck' 18 import {Text} from '../Typography' 19 ··· 108 const containerStyle = React.useMemo(() => { 109 let left = 10 110 let right = 10 111 - if (isWeb && winDim.width > 400) { 112 left = right = (winDim.width - 380) / 2 113 } 114 return { 115 - position: isWeb ? 'fixed' : 'absolute', 116 top: 0, 117 left, 118 right,
··· 11 import {msg} from '@lingui/macro' 12 import {useLingui} from '@lingui/react' 13 14 import {atoms as a, useTheme} from '#/alf' 15 import {Portal} from '#/components/Portal' 16 + import {IS_WEB} from '#/env' 17 import {AnimatedCheck, type AnimatedCheckRef} from '../anim/AnimatedCheck' 18 import {Text} from '../Typography' 19 ··· 108 const containerStyle = React.useMemo(() => { 109 let left = 10 110 let right = 10 111 + if (IS_WEB && winDim.width > 400) { 112 left = right = (winDim.width - 380) / 2 113 } 114 return { 115 + position: IS_WEB ? 'fixed' : 'absolute', 116 top: 0, 117 left, 118 right,
+5 -5
src/components/RichTextTag.tsx
··· 6 7 import {type NavigationProp} from '#/lib/routes/types' 8 import {isInvalidHandle} from '#/lib/strings/handles' 9 - import {isNative, isWeb} from '#/platform/detection' 10 import { 11 usePreferencesQuery, 12 useRemoveMutedWordsMutation, ··· 22 } from '#/components/Link' 23 import {Loader} from '#/components/Loader' 24 import * as Menu from '#/components/Menu' 25 26 export function RichTextTag({ 27 tag, ··· 50 const navigation = useNavigation<NavigationProp>() 51 const isCashtag = tag.startsWith('$') 52 const label = isCashtag ? _(msg`Cashtag ${tag}`) : _(msg`Hashtag ${tag}`) 53 - const hint = isNative 54 ? _(msg`Long press to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 55 : _(msg`Click to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 56 ··· 86 }} 87 {...menuProps} 88 onPress={e => { 89 - if (isWeb) { 90 return createStaticClickIfUnmodified(() => { 91 - if (!isNative) { 92 menuProps.onPress() 93 } 94 }).onPress(e) ··· 99 label={label} 100 style={textStyle} 101 emoji> 102 - {isNative ? ( 103 display 104 ) : ( 105 <RNText ref={menuProps.ref}>{display}</RNText>
··· 6 7 import {type NavigationProp} from '#/lib/routes/types' 8 import {isInvalidHandle} from '#/lib/strings/handles' 9 import { 10 usePreferencesQuery, 11 useRemoveMutedWordsMutation, ··· 21 } from '#/components/Link' 22 import {Loader} from '#/components/Loader' 23 import * as Menu from '#/components/Menu' 24 + import {IS_NATIVE, IS_WEB} from '#/env' 25 26 export function RichTextTag({ 27 tag, ··· 50 const navigation = useNavigation<NavigationProp>() 51 const isCashtag = tag.startsWith('$') 52 const label = isCashtag ? _(msg`Cashtag ${tag}`) : _(msg`Hashtag ${tag}`) 53 + const hint = IS_NATIVE 54 ? _(msg`Long press to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 55 : _(msg`Click to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 56 ··· 86 }} 87 {...menuProps} 88 onPress={e => { 89 + if (IS_WEB) { 90 return createStaticClickIfUnmodified(() => { 91 + if (!IS_NATIVE) { 92 menuProps.onPress() 93 } 94 }).onPress(e) ··· 99 label={label} 100 style={textStyle} 101 emoji> 102 + {IS_NATIVE ? ( 103 display 104 ) : ( 105 <RNText ref={menuProps.ref}>{display}</RNText>
+3 -3
src/components/ScreenTransition.tsx
··· 8 } from 'react-native-reanimated' 9 import type React from 'react' 10 11 - import {isWeb} from '#/platform/detection' 12 13 export function ScreenTransition({ 14 direction, ··· 31 32 return ( 33 <Animated.View 34 - entering={isWeb ? webEntering : entering} 35 - exiting={isWeb ? webExiting : exiting} 36 style={style}> 37 {children} 38 </Animated.View>
··· 8 } from 'react-native-reanimated' 9 import type React from 'react' 10 11 + import {IS_WEB} from '#/env' 12 13 export function ScreenTransition({ 14 direction, ··· 31 32 return ( 33 <Animated.View 34 + entering={IS_WEB ? webEntering : entering} 35 + exiting={IS_WEB ? webExiting : exiting} 36 style={style}> 37 {children} 38 </Animated.View>
+1 -1
src/components/Select/index.tsx
··· 82 83 if (typeof children === 'function') { 84 return children({ 85 - isNative: true, 86 control, 87 state: { 88 hovered: false,
··· 82 83 if (typeof children === 'function') { 84 return children({ 85 + IS_NATIVE: true, 86 control, 87 state: { 88 hovered: false,
+1 -1
src/components/Select/index.web.tsx
··· 68 <RadixTriggerPassThrough> 69 {props => 70 children({ 71 - isNative: false, 72 state: { 73 hovered, 74 focused,
··· 68 <RadixTriggerPassThrough> 69 {props => 70 children({ 71 + IS_NATIVE: false, 72 state: { 73 hovered, 74 focused,
+2 -2
src/components/Select/types.ts
··· 65 66 export type TriggerChildProps = 67 | { 68 - isNative: true 69 control: DialogControlProps 70 state: { 71 /** ··· 92 } 93 } 94 | { 95 - isNative: false 96 state: { 97 hovered: boolean 98 focused: boolean
··· 65 66 export type TriggerChildProps = 67 | { 68 + IS_NATIVE: true 69 control: DialogControlProps 70 state: { 71 /** ··· 92 } 93 } 94 | { 95 + IS_NATIVE: false 96 state: { 97 hovered: boolean 98 focused: boolean
+3 -3
src/components/StarterPack/Main/FeedsList.tsx
··· 3 import {type AppBskyFeedDefs} from '@atproto/api' 4 5 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 6 - import {isNative, isWeb} from '#/platform/detection' 7 import {List, type ListRef} from '#/view/com/util/List' 8 import {type SectionRef} from '#/screens/Profile/Sections/types' 9 import {atoms as a, useTheme} from '#/alf' 10 import * as FeedCard from '#/components/FeedCard' 11 12 function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { 13 return item.uri ··· 27 28 const onScrollToTop = useCallback(() => { 29 scrollElRef.current?.scrollToOffset({ 30 - animated: isNative, 31 offset: -headerHeight, 32 }) 33 }, [scrollElRef, headerHeight]) ··· 44 <View 45 style={[ 46 a.p_lg, 47 - (isWeb || index !== 0) && a.border_t, 48 t.atoms.border_contrast_low, 49 ]}> 50 <FeedCard.Default view={item} />
··· 3 import {type AppBskyFeedDefs} from '@atproto/api' 4 5 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 6 import {List, type ListRef} from '#/view/com/util/List' 7 import {type SectionRef} from '#/screens/Profile/Sections/types' 8 import {atoms as a, useTheme} from '#/alf' 9 import * as FeedCard from '#/components/FeedCard' 10 + import {IS_NATIVE, IS_WEB} from '#/env' 11 12 function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { 13 return item.uri ··· 27 28 const onScrollToTop = useCallback(() => { 29 scrollElRef.current?.scrollToOffset({ 30 + animated: IS_NATIVE, 31 offset: -headerHeight, 32 }) 33 }, [scrollElRef, headerHeight]) ··· 44 <View 45 style={[ 46 a.p_lg, 47 + (IS_WEB || index !== 0) && a.border_t, 48 t.atoms.border_contrast_low, 49 ]}> 50 <FeedCard.Default view={item} />
+2 -2
src/components/StarterPack/Main/PostsList.tsx
··· 3 import {msg} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 - import {isNative} from '#/platform/detection' 7 import {type FeedDescriptor} from '#/state/queries/post-feed' 8 import {PostFeed} from '#/view/com/posts/PostFeed' 9 import {EmptyState} from '#/view/com/util/EmptyState' 10 import {type ListRef} from '#/view/com/util/List' 11 import {type SectionRef} from '#/screens/Profile/Sections/types' 12 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 13 14 interface ProfilesListProps { 15 listUri: string ··· 24 25 const onScrollToTop = useCallback(() => { 26 scrollElRef.current?.scrollToOffset({ 27 - animated: isNative, 28 offset: -headerHeight, 29 }) 30 }, [scrollElRef, headerHeight])
··· 3 import {msg} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import {type FeedDescriptor} from '#/state/queries/post-feed' 7 import {PostFeed} from '#/view/com/posts/PostFeed' 8 import {EmptyState} from '#/view/com/util/EmptyState' 9 import {type ListRef} from '#/view/com/util/List' 10 import {type SectionRef} from '#/screens/Profile/Sections/types' 11 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 12 + import {IS_NATIVE} from '#/env' 13 14 interface ProfilesListProps { 15 listUri: string ··· 24 25 const onScrollToTop = useCallback(() => { 26 scrollElRef.current?.scrollToOffset({ 27 + animated: IS_NATIVE, 28 offset: -headerHeight, 29 }) 30 }, [scrollElRef, headerHeight])
+3 -3
src/components/StarterPack/Main/ProfilesList.tsx
··· 14 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 15 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 16 import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' 17 - import {isNative, isWeb} from '#/platform/detection' 18 import {useAllListMembersQuery} from '#/state/queries/list-members' 19 import {useSession} from '#/state/session' 20 import {List, type ListRef} from '#/view/com/util/List' ··· 22 import {atoms as a, useTheme} from '#/alf' 23 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 24 import {Default as ProfileCard} from '#/components/ProfileCard' 25 26 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) { 27 return `${item.did}-${index}` ··· 75 } 76 const onScrollToTop = useCallback(() => { 77 scrollElRef.current?.scrollToOffset({ 78 - animated: isNative, 79 offset: -headerHeight, 80 }) 81 }, [scrollElRef, headerHeight]) ··· 93 style={[ 94 a.p_lg, 95 t.atoms.border_contrast_low, 96 - (isWeb || index !== 0) && a.border_t, 97 ]}> 98 <ProfileCard 99 profile={item}
··· 14 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 15 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 16 import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' 17 import {useAllListMembersQuery} from '#/state/queries/list-members' 18 import {useSession} from '#/state/session' 19 import {List, type ListRef} from '#/view/com/util/List' ··· 21 import {atoms as a, useTheme} from '#/alf' 22 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 23 import {Default as ProfileCard} from '#/components/ProfileCard' 24 + import {IS_NATIVE, IS_WEB} from '#/env' 25 26 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) { 27 return `${item.did}-${index}` ··· 75 } 76 const onScrollToTop = useCallback(() => { 77 scrollElRef.current?.scrollToOffset({ 78 + animated: IS_NATIVE, 79 offset: -headerHeight, 80 }) 81 }, [scrollElRef, headerHeight]) ··· 93 style={[ 94 a.p_lg, 95 t.atoms.border_contrast_low, 96 + (IS_WEB || index !== 0) && a.border_t, 97 ]}> 98 <ProfileCard 99 profile={item}
+2 -2
src/components/StarterPack/ProfileStarterPacks.tsx
··· 19 import {type NavigationProp} from '#/lib/routes/types' 20 import {parseStarterPackUri} from '#/lib/strings/starter-pack' 21 import {logger} from '#/logger' 22 - import {isIOS} from '#/platform/detection' 23 import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' 24 import { 25 EmptyState, ··· 36 import * as Prompt from '#/components/Prompt' 37 import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' 38 import {Text} from '#/components/Typography' 39 40 interface SectionRef { 41 scrollToTop: () => void ··· 136 }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 137 138 useEffect(() => { 139 - if (isIOS && enabled && scrollElRef.current) { 140 const nativeTag = findNodeHandle(scrollElRef.current) 141 setScrollViewTag(nativeTag) 142 }
··· 19 import {type NavigationProp} from '#/lib/routes/types' 20 import {parseStarterPackUri} from '#/lib/strings/starter-pack' 21 import {logger} from '#/logger' 22 import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' 23 import { 24 EmptyState, ··· 35 import * as Prompt from '#/components/Prompt' 36 import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' 37 import {Text} from '#/components/Typography' 38 + import {IS_IOS} from '#/env' 39 40 interface SectionRef { 41 scrollToTop: () => void ··· 136 }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 137 138 useEffect(() => { 139 + if (IS_IOS && enabled && scrollElRef.current) { 140 const nativeTag = findNodeHandle(scrollElRef.current) 141 setScrollViewTag(nativeTag) 142 }
+6 -6
src/components/StarterPack/QrCode.tsx
··· 6 import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 7 import {Trans} from '@lingui/macro' 8 9 - import {isWeb} from '#/platform/detection' 10 import {Logo} from '#/view/icons/Logo' 11 import {Logotype} from '#/view/icons/Logotype' 12 import {useTheme} from '#/alf' 13 import {atoms as a} from '#/alf' 14 import {LinearGradientBackground} from '#/components/LinearGradientBackground' 15 import {Text} from '#/components/Typography' 16 import * as bsky from '#/types/bsky' 17 18 const LazyViewShot = lazy( ··· 121 return ( 122 <View style={{position: 'relative'}}> 123 {/* An SVG version of the logo is placed on top of normal `QRCode` `logo` prop, since the PNG fails to load before the export completes on web. */} 124 - {isWeb && logoArea && ( 125 <View 126 style={{ 127 position: 'absolute', ··· 139 a.rounded_sm, 140 {height: 225, width: 225, backgroundColor: '#f3f3f3'}, 141 ]} 142 - pieceSize={isWeb ? 8 : 6} 143 padding={20} 144 - pieceBorderRadius={isWeb ? 4.5 : 3.5} 145 outerEyesOptions={{ 146 topLeft: { 147 borderRadius: [12, 12, 0, 12], ··· 159 innerEyesOptions={{borderRadius: 3}} 160 logo={{ 161 href: require('../../../assets/logo.png'), 162 - ...(isWeb && { 163 onChange: onLogoAreaChange, 164 padding: 28, 165 }), 166 - ...(!isWeb && { 167 padding: 2, 168 scale: 0.95, 169 }),
··· 6 import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 7 import {Trans} from '@lingui/macro' 8 9 import {Logo} from '#/view/icons/Logo' 10 import {Logotype} from '#/view/icons/Logotype' 11 import {useTheme} from '#/alf' 12 import {atoms as a} from '#/alf' 13 import {LinearGradientBackground} from '#/components/LinearGradientBackground' 14 import {Text} from '#/components/Typography' 15 + import {IS_WEB} from '#/env' 16 import * as bsky from '#/types/bsky' 17 18 const LazyViewShot = lazy( ··· 121 return ( 122 <View style={{position: 'relative'}}> 123 {/* An SVG version of the logo is placed on top of normal `QRCode` `logo` prop, since the PNG fails to load before the export completes on web. */} 124 + {IS_WEB && logoArea && ( 125 <View 126 style={{ 127 position: 'absolute', ··· 139 a.rounded_sm, 140 {height: 225, width: 225, backgroundColor: '#f3f3f3'}, 141 ]} 142 + pieceSize={IS_WEB ? 8 : 6} 143 padding={20} 144 + pieceBorderRadius={IS_WEB ? 4.5 : 3.5} 145 outerEyesOptions={{ 146 topLeft: { 147 borderRadius: [12, 12, 0, 12], ··· 159 innerEyesOptions={{borderRadius: 3}} 160 logo={{ 161 href: require('../../../assets/logo.png'), 162 + ...(IS_WEB && { 163 onChange: onLogoAreaChange, 164 padding: 28, 165 }), 166 + ...(!IS_WEB && { 167 padding: 2, 168 scale: 0.95, 169 }),
+6 -6
src/components/StarterPack/QrCodeDialog.tsx
··· 9 import {useLingui} from '@lingui/react' 10 11 import {logger} from '#/logger' 12 - import {isNative, isWeb} from '#/platform/detection' 13 import {atoms as a, useBreakpoints} from '#/alf' 14 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 import * as Dialog from '#/components/Dialog' ··· 20 import {Loader} from '#/components/Loader' 21 import {QrCode} from '#/components/StarterPack/QrCode' 22 import * as Toast from '#/components/Toast' 23 import * as bsky from '#/types/bsky' 24 25 export function QrCodeDialog({ ··· 56 57 const onSavePress = async () => { 58 ref.current?.capture?.().then(async (uri: string) => { 59 - if (isNative) { 60 const res = await requestMediaLibraryPermissionsAsync() 61 62 if (!res.granted) { ··· 111 }) 112 setIsSaveProcessing(false) 113 Toast.show( 114 - isWeb 115 ? _(msg`QR code has been downloaded!`) 116 : _(msg`QR code saved to your camera roll!`), 117 ) ··· 178 label={_(msg`Copy QR code`)} 179 color="primary_subtle" 180 size="large" 181 - onPress={isWeb ? onCopyPress : onSharePress}> 182 <ButtonIcon 183 icon={ 184 isCopyProcessing 185 ? Loader 186 - : isWeb 187 ? ChainLinkIcon 188 : ShareIcon 189 } 190 /> 191 <ButtonText> 192 - {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 193 </ButtonText> 194 </Button> 195 <Button
··· 9 import {useLingui} from '@lingui/react' 10 11 import {logger} from '#/logger' 12 import {atoms as a, useBreakpoints} from '#/alf' 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 import * as Dialog from '#/components/Dialog' ··· 19 import {Loader} from '#/components/Loader' 20 import {QrCode} from '#/components/StarterPack/QrCode' 21 import * as Toast from '#/components/Toast' 22 + import {IS_NATIVE, IS_WEB} from '#/env' 23 import * as bsky from '#/types/bsky' 24 25 export function QrCodeDialog({ ··· 56 57 const onSavePress = async () => { 58 ref.current?.capture?.().then(async (uri: string) => { 59 + if (IS_NATIVE) { 60 const res = await requestMediaLibraryPermissionsAsync() 61 62 if (!res.granted) { ··· 111 }) 112 setIsSaveProcessing(false) 113 Toast.show( 114 + IS_WEB 115 ? _(msg`QR code has been downloaded!`) 116 : _(msg`QR code saved to your camera roll!`), 117 ) ··· 178 label={_(msg`Copy QR code`)} 179 color="primary_subtle" 180 size="large" 181 + onPress={IS_WEB ? onCopyPress : onSharePress}> 182 <ButtonIcon 183 icon={ 184 isCopyProcessing 185 ? Loader 186 + : IS_WEB 187 ? ChainLinkIcon 188 : ShareIcon 189 } 190 /> 191 <ButtonText> 192 + {IS_WEB ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 193 </ButtonText> 194 </Button> 195 <Button
+8 -4
src/components/StarterPack/ShareDialog.tsx
··· 8 import {shareUrl} from '#/lib/sharing' 9 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 10 import {logger} from '#/logger' 11 - import {isNative, isWeb} from '#/platform/detection' 12 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 import {type DialogControlProps} from '#/components/Dialog' ··· 18 import {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode' 19 import {Loader} from '#/components/Loader' 20 import {Text} from '#/components/Typography' 21 22 interface Props { 23 starterPack: AppBskyGraphDefs.StarterPackView ··· 110 ], 111 ]}> 112 <Button 113 - label={isWeb ? _(msg`Copy link`) : _(msg`Share link`)} 114 color="primary_subtle" 115 size="large" 116 onPress={onShareLink}> 117 <ButtonIcon icon={ChainLinkIcon} /> 118 <ButtonText> 119 - {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share link</Trans>} 120 </ButtonText> 121 </Button> 122 <Button ··· 133 <Trans>Share QR code</Trans> 134 </ButtonText> 135 </Button> 136 - {isNative && ( 137 <Button 138 label={_(msg`Save image`)} 139 color="secondary"
··· 8 import {shareUrl} from '#/lib/sharing' 9 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 10 import {logger} from '#/logger' 11 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 12 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 13 import {type DialogControlProps} from '#/components/Dialog' ··· 17 import {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode' 18 import {Loader} from '#/components/Loader' 19 import {Text} from '#/components/Typography' 20 + import {IS_NATIVE, IS_WEB} from '#/env' 21 22 interface Props { 23 starterPack: AppBskyGraphDefs.StarterPackView ··· 110 ], 111 ]}> 112 <Button 113 + label={IS_WEB ? _(msg`Copy link`) : _(msg`Share link`)} 114 color="primary_subtle" 115 size="large" 116 onPress={onShareLink}> 117 <ButtonIcon icon={ChainLinkIcon} /> 118 <ButtonText> 119 + {IS_WEB ? ( 120 + <Trans>Copy Link</Trans> 121 + ) : ( 122 + <Trans>Share link</Trans> 123 + )} 124 </ButtonText> 125 </Button> 126 <Button ··· 137 <Trans>Share QR code</Trans> 138 </ButtonText> 139 </Button> 140 + {IS_NATIVE && ( 141 <Button 142 label={_(msg`Save image`)} 143 color="secondary"
+3 -3
src/components/StarterPack/Wizard/WizardEditListDialog.tsx
··· 10 import {useLingui} from '@lingui/react' 11 12 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 13 - import {isWeb} from '#/platform/detection' 14 import {type ListMethods} from '#/view/com/util/List' 15 import { 16 type WizardAction, ··· 24 WizardProfileCard, 25 } from '#/components/StarterPack/Wizard/WizardListCard' 26 import {Text} from '#/components/Typography' 27 28 function keyExtractor( 29 item: AppBskyActorDefs.ProfileViewBasic | AppBskyFeedDefs.GeneratorView, ··· 95 a.mb_sm, 96 t.atoms.bg, 97 t.atoms.border_contrast_medium, 98 - isWeb 99 ? [ 100 a.align_center, 101 { ··· 113 )} 114 </Text> 115 <View style={{width: 60}}> 116 - {isWeb && ( 117 <Button 118 label={_(msg`Close`)} 119 variant="ghost"
··· 10 import {useLingui} from '@lingui/react' 11 12 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 13 import {type ListMethods} from '#/view/com/util/List' 14 import { 15 type WizardAction, ··· 23 WizardProfileCard, 24 } from '#/components/StarterPack/Wizard/WizardListCard' 25 import {Text} from '#/components/Typography' 26 + import {IS_WEB} from '#/env' 27 28 function keyExtractor( 29 item: AppBskyActorDefs.ProfileViewBasic | AppBskyFeedDefs.GeneratorView, ··· 95 a.mb_sm, 96 t.atoms.bg, 97 t.atoms.border_contrast_medium, 98 + IS_WEB 99 ? [ 100 a.align_center, 101 { ··· 113 )} 114 </Text> 115 <View style={{width: 60}}> 116 + {IS_WEB && ( 117 <Button 118 label={_(msg`Close`)} 119 variant="ghost"
+4 -5
src/components/SubtleHover.tsx
··· 1 import {View} from 'react-native' 2 3 - import {isTouchDevice} from '#/lib/browser' 4 - import {isNative, isWeb} from '#/platform/detection' 5 import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 6 7 export function SubtleHover({ 8 style, ··· 39 /> 40 ) 41 42 - if (isWeb && web) { 43 - return isTouchDevice ? null : el 44 - } else if (isNative && native) { 45 return el 46 } 47
··· 1 import {View} from 'react-native' 2 3 import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 4 + import {IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' 5 6 export function SubtleHover({ 7 style, ··· 38 /> 39 ) 40 41 + if (IS_WEB && web) { 42 + return IS_WEB_TOUCH_DEVICE ? null : el 43 + } else if (IS_NATIVE && native) { 44 return el 45 } 46
+1
src/components/Typography.tsx
··· 35 if (__DEV__) { 36 if (!emoji && childHasEmoji(children)) { 37 logger.warn( 38 `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`, 39 ) 40 }
··· 35 if (__DEV__) { 36 if (!emoji && childHasEmoji(children)) { 37 logger.warn( 38 + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string 39 `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`, 40 ) 41 }
+3 -3
src/components/WhoCanReply.tsx
··· 18 import {HITSLOP_10} from '#/lib/constants' 19 import {makeListLink, makeProfileLink} from '#/lib/routes/links' 20 import {logger} from '#/logger' 21 - import {isNative} from '#/platform/detection' 22 import { 23 type ThreadgateAllowUISetting, 24 threadgateViewToAllowUISetting, ··· 37 import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' 38 import {InlineLinkText} from '#/components/Link' 39 import {Text} from '#/components/Typography' 40 import * as bsky from '#/types/bsky' 41 42 interface WhoCanReplyProps { ··· 86 : _(msg`Some people can reply`) 87 88 const onPressOpen = () => { 89 - if (isNative && Keyboard.isVisible()) { 90 Keyboard.dismiss() 91 } 92 if (isThreadAuthor) { ··· 229 embeddingDisabled={embeddingDisabled} 230 /> 231 </View> 232 - {isNative && ( 233 <Button 234 label={_(msg`Close`)} 235 onPress={() => control.close()}
··· 18 import {HITSLOP_10} from '#/lib/constants' 19 import {makeListLink, makeProfileLink} from '#/lib/routes/links' 20 import {logger} from '#/logger' 21 import { 22 type ThreadgateAllowUISetting, 23 threadgateViewToAllowUISetting, ··· 36 import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' 37 import {InlineLinkText} from '#/components/Link' 38 import {Text} from '#/components/Typography' 39 + import {IS_NATIVE} from '#/env' 40 import * as bsky from '#/types/bsky' 41 42 interface WhoCanReplyProps { ··· 86 : _(msg`Some people can reply`) 87 88 const onPressOpen = () => { 89 + if (IS_NATIVE && Keyboard.isVisible()) { 90 Keyboard.dismiss() 91 } 92 if (isThreadAuthor) { ··· 229 embeddingDisabled={embeddingDisabled} 230 /> 231 </View> 232 + {IS_NATIVE && ( 233 <Button 234 label={_(msg`Close`)} 235 onPress={() => control.close()}
+2 -2
src/components/activity-notifications/SubscribeProfileDialog.tsx
··· 18 import {cleanError} from '#/lib/strings/errors' 19 import {sanitizeHandle} from '#/lib/strings/handles' 20 import {logger} from '#/logger' 21 - import {isWeb} from '#/platform/detection' 22 import {updateProfileShadow} from '#/state/cache/profile-shadow' 23 import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions' 24 import {useAgent} from '#/state/session' ··· 37 import {Loader} from '#/components/Loader' 38 import * as ProfileCard from '#/components/ProfileCard' 39 import {Text} from '#/components/Typography' 40 import type * as bsky from '#/types/bsky' 41 42 export function SubscribeProfileDialog({ ··· 195 } 196 } else { 197 // on web, a disabled save button feels more natural than a massive close button 198 - if (isWeb) { 199 return { 200 label: _(msg`Save changes`), 201 color: 'secondary',
··· 18 import {cleanError} from '#/lib/strings/errors' 19 import {sanitizeHandle} from '#/lib/strings/handles' 20 import {logger} from '#/logger' 21 import {updateProfileShadow} from '#/state/cache/profile-shadow' 22 import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions' 23 import {useAgent} from '#/state/session' ··· 36 import {Loader} from '#/components/Loader' 37 import * as ProfileCard from '#/components/ProfileCard' 38 import {Text} from '#/components/Typography' 39 + import {IS_WEB} from '#/env' 40 import type * as bsky from '#/types/bsky' 41 42 export function SubscribeProfileDialog({ ··· 195 } 196 } else { 197 // on web, a disabled save button feels more natural than a massive close button 198 + if (IS_WEB) { 199 return { 200 label: _(msg`Save changes`), 201 color: 'secondary',
+2 -2
src/components/ageAssurance/AgeAssuranceAccountCard.tsx
··· 3 import {useLingui} from '@lingui/react' 4 5 import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 6 - import {isNative} from '#/platform/detection' 7 import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' 8 import {Admonition} from '#/components/Admonition' 9 import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' ··· 23 import {Text} from '#/components/Typography' 24 import {logger, useAgeAssurance} from '#/ageAssurance' 25 import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' 26 import {useDeviceGeolocationApi} from '#/geolocation' 27 28 export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { ··· 86 <View style={[a.pb_md, a.gap_xs]}> 87 <Text style={[a.text_sm, a.leading_snug]}>{copy.notice}</Text> 88 89 - {isNative && ( 90 <> 91 <Text style={[a.text_sm, a.leading_snug]}> 92 <Trans>
··· 3 import {useLingui} from '@lingui/react' 4 5 import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 6 import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' 7 import {Admonition} from '#/components/Admonition' 8 import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' ··· 22 import {Text} from '#/components/Typography' 23 import {logger, useAgeAssurance} from '#/ageAssurance' 24 import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' 25 + import {IS_NATIVE} from '#/env' 26 import {useDeviceGeolocationApi} from '#/geolocation' 27 28 export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { ··· 86 <View style={[a.pb_md, a.gap_xs]}> 87 <Text style={[a.text_sm, a.leading_snug]}>{copy.notice}</Text> 88 89 + {IS_NATIVE && ( 90 <> 91 <Text style={[a.text_sm, a.leading_snug]}> 92 <Trans>
+3 -3
src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx
··· 5 6 import {retry} from '#/lib/async/retry' 7 import {wait} from '#/lib/async/wait' 8 - import {isNative} from '#/platform/detection' 9 import {useAgent} from '#/state/session' 10 import {atoms as a, useTheme, web} from '#/alf' 11 import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' ··· 18 import {Text} from '#/components/Typography' 19 import {refetchAgeAssuranceServerState} from '#/ageAssurance' 20 import {logger} from '#/ageAssurance' 21 22 export type AgeAssuranceRedirectDialogState = { 23 result: 'success' | 'unknown' ··· 166 </Trans> 167 </Text> 168 169 - {isNative && ( 170 <View style={[a.w_full, a.pt_lg]}> 171 <Button 172 label={_(msg`Close`)} ··· 225 )} 226 </Text> 227 228 - {error && isNative && ( 229 <View style={[a.w_full, a.pt_lg]}> 230 <Button 231 label={_(msg`Close`)}
··· 5 6 import {retry} from '#/lib/async/retry' 7 import {wait} from '#/lib/async/wait' 8 import {useAgent} from '#/state/session' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' ··· 17 import {Text} from '#/components/Typography' 18 import {refetchAgeAssuranceServerState} from '#/ageAssurance' 19 import {logger} from '#/ageAssurance' 20 + import {IS_NATIVE} from '#/env' 21 22 export type AgeAssuranceRedirectDialogState = { 23 result: 'success' | 'unknown' ··· 166 </Trans> 167 </Text> 168 169 + {IS_NATIVE && ( 170 <View style={[a.w_full, a.pt_lg]}> 171 <Button 172 label={_(msg`Close`)} ··· 225 )} 226 </Text> 227 228 + {error && IS_NATIVE && ( 229 <View style={[a.w_full, a.pt_lg]}> 230 <Button 231 label={_(msg`Close`)}
+2 -2
src/components/contacts/FindContactsBannerNUX.tsx
··· 8 import {HITSLOP_10} from '#/lib/constants' 9 import {useGate} from '#/lib/statsig/statsig' 10 import {logger} from '#/logger' 11 - import {isWeb} from '#/platform/detection' 12 import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' 13 import {atoms as a, useTheme} from '#/alf' 14 import {Button} from '#/components/Button' 15 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 16 import {Text} from '#/components/Typography' 17 import {Link} from '../Link' 18 import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allowlist' 19 ··· 92 const gate = useGate() 93 94 const visible = useMemo(() => { 95 - if (isWeb) return false 96 if (hidden) return false 97 if (nux && nux.completed) return false 98 if (!isFeatureEnabled) return false
··· 8 import {HITSLOP_10} from '#/lib/constants' 9 import {useGate} from '#/lib/statsig/statsig' 10 import {logger} from '#/logger' 11 import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' 12 import {atoms as a, useTheme} from '#/alf' 13 import {Button} from '#/components/Button' 14 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 15 import {Text} from '#/components/Typography' 16 + import {IS_WEB} from '#/env' 17 import {Link} from '../Link' 18 import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allowlist' 19 ··· 92 const gate = useGate() 93 94 const visible = useMemo(() => { 95 + if (IS_WEB) return false 96 if (hidden) return false 97 if (nux && nux.completed) return false 98 if (!isFeatureEnabled) return false
+3 -3
src/components/contacts/components/OTPInput.tsx
··· 9 import {useLingui} from '@lingui/react' 10 11 import {mergeRefs} from '#/lib/merge-refs' 12 - import {isAndroid, isIOS} from '#/platform/detection' 13 import {atoms as a, ios, platform, useTheme} from '#/alf' 14 import {useInteractionState} from '#/components/hooks/useInteractionState' 15 import {Text} from '#/components/Typography' 16 17 export function OTPInput({ 18 label, ··· 95 <TextInput 96 // SMS autofill is borked on iOS if you open the keyboard immediately -sfn 97 onLayout={ios(() => setTimeout(() => innerRef.current?.focus(), 100))} 98 - autoFocus={isAndroid} 99 accessible 100 accessibilityLabel={label} 101 accessibilityHint="" ··· 135 android: {opacity: 0}, 136 }), 137 ]} 138 - caretHidden={isIOS} 139 clearTextOnFocus 140 /> 141 </Pressable>
··· 9 import {useLingui} from '@lingui/react' 10 11 import {mergeRefs} from '#/lib/merge-refs' 12 import {atoms as a, ios, platform, useTheme} from '#/alf' 13 import {useInteractionState} from '#/components/hooks/useInteractionState' 14 import {Text} from '#/components/Typography' 15 + import {IS_ANDROID, IS_IOS} from '#/env' 16 17 export function OTPInput({ 18 label, ··· 95 <TextInput 96 // SMS autofill is borked on iOS if you open the keyboard immediately -sfn 97 onLayout={ios(() => setTimeout(() => innerRef.current?.focus(), 100))} 98 + autoFocus={IS_ANDROID} 99 accessible 100 accessibilityLabel={label} 101 accessibilityHint="" ··· 135 android: {opacity: 0}, 136 }), 137 ]} 138 + caretHidden={IS_IOS} 139 clearTextOnFocus 140 /> 141 </Pressable>
+3 -3
src/components/dialogs/BirthDateSettings.tsx
··· 7 import {isAppPassword} from '#/lib/jwt' 8 import {getAge, getDateAgo} from '#/lib/strings/time' 9 import {logger} from '#/logger' 10 - import {isIOS, isWeb} from '#/platform/detection' 11 import { 12 useBirthdateMutation, 13 useIsBirthdateUpdateAllowed, ··· 26 import {SimpleInlineLinkText} from '#/components/Link' 27 import {Loader} from '#/components/Loader' 28 import {Span, Text} from '#/components/Typography' 29 30 export function BirthDateSettingsDialog({ 31 control, ··· 154 155 return ( 156 <View style={a.gap_lg} testID="birthDateSettingsDialog"> 157 - <View style={isIOS && [a.w_full, a.align_center]}> 158 <DateField 159 testID="birthdayInput" 160 value={date} ··· 191 <ErrorMessage message={errorMessage} style={[a.rounded_sm]} /> 192 ) : undefined} 193 194 - <View style={isWeb && [a.flex_row, a.justify_end]}> 195 <Button 196 label={hasChanged ? _(msg`Save birthdate`) : _(msg`Done`)} 197 size="large"
··· 7 import {isAppPassword} from '#/lib/jwt' 8 import {getAge, getDateAgo} from '#/lib/strings/time' 9 import {logger} from '#/logger' 10 import { 11 useBirthdateMutation, 12 useIsBirthdateUpdateAllowed, ··· 25 import {SimpleInlineLinkText} from '#/components/Link' 26 import {Loader} from '#/components/Loader' 27 import {Span, Text} from '#/components/Typography' 28 + import {IS_IOS, IS_WEB} from '#/env' 29 30 export function BirthDateSettingsDialog({ 31 control, ··· 154 155 return ( 156 <View style={a.gap_lg} testID="birthDateSettingsDialog"> 157 + <View style={IS_IOS && [a.w_full, a.align_center]}> 158 <DateField 159 testID="birthdayInput" 160 value={date} ··· 191 <ErrorMessage message={errorMessage} style={[a.rounded_sm]} /> 192 ) : undefined} 193 194 + <View style={IS_WEB && [a.flex_row, a.justify_end]}> 195 <Button 196 label={hasChanged ? _(msg`Save birthdate`) : _(msg`Done`)} 197 size="large"
+4 -4
src/components/dialogs/DeviceLocationRequestDialog.tsx
··· 6 import {wait} from '#/lib/async/wait' 7 import {isNetworkError, useCleanError} from '#/lib/hooks/useCleanError' 8 import {logger} from '#/logger' 9 - import {isWeb} from '#/platform/detection' 10 import {atoms as a, useTheme, web} from '#/alf' 11 import {Admonition} from '#/components/Admonition' 12 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 14 import {PinLocation_Stroke2_Corner0_Rounded as LocationIcon} from '#/components/icons/PinLocation' 15 import {Loader} from '#/components/Loader' 16 import {Text} from '#/components/Typography' 17 import {type Geolocation, useRequestDeviceGeolocation} from '#/geolocation' 18 19 export type Props = { ··· 138 disabled={isRequesting} 139 label={_(msg`Allow location access`)} 140 onPress={onPressConfirm} 141 - size={isWeb ? 'small' : 'large'} 142 color="primary"> 143 <ButtonIcon icon={isRequesting ? Loader : LocationIcon} /> 144 <ButtonText> ··· 147 </Button> 148 )} 149 150 - {!isWeb && ( 151 <Button 152 label={_(msg`Cancel`)} 153 onPress={() => close()} 154 - size={isWeb ? 'small' : 'large'} 155 color="secondary"> 156 <ButtonText> 157 <Trans>Cancel</Trans>
··· 6 import {wait} from '#/lib/async/wait' 7 import {isNetworkError, useCleanError} from '#/lib/hooks/useCleanError' 8 import {logger} from '#/logger' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {Admonition} from '#/components/Admonition' 11 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 13 import {PinLocation_Stroke2_Corner0_Rounded as LocationIcon} from '#/components/icons/PinLocation' 14 import {Loader} from '#/components/Loader' 15 import {Text} from '#/components/Typography' 16 + import {IS_WEB} from '#/env' 17 import {type Geolocation, useRequestDeviceGeolocation} from '#/geolocation' 18 19 export type Props = { ··· 138 disabled={isRequesting} 139 label={_(msg`Allow location access`)} 140 onPress={onPressConfirm} 141 + size={IS_WEB ? 'small' : 'large'} 142 color="primary"> 143 <ButtonIcon icon={isRequesting ? Loader : LocationIcon} /> 144 <ButtonText> ··· 147 </Button> 148 )} 149 150 + {!IS_WEB && ( 151 <Button 152 label={_(msg`Cancel`)} 153 onPress={() => close()} 154 + size={IS_WEB ? 'small' : 'large'} 155 color="secondary"> 156 <ButtonText> 157 <Trans>Cancel</Trans>
+3 -3
src/components/dialogs/GifSelect.tsx
··· 13 14 import {logEvent} from '#/lib/statsig/statsig' 15 import {cleanError} from '#/lib/strings/errors' 16 - import {isWeb} from '#/platform/detection' 17 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 18 import { 19 type Gif, ··· 32 import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' 33 import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' 34 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 35 36 export function GifSelectDialog({ 37 controlRef, ··· 152 a.pb_sm, 153 t.atoms.bg, 154 ]}> 155 - {!gtMobile && isWeb && ( 156 <Button 157 size="small" 158 variant="ghost" ··· 164 </Button> 165 )} 166 167 - <TextField.Root style={[!gtMobile && isWeb && a.flex_1]}> 168 <TextField.Icon icon={Search} /> 169 <TextField.Input 170 label={_(msg`Search GIFs`)}
··· 13 14 import {logEvent} from '#/lib/statsig/statsig' 15 import {cleanError} from '#/lib/strings/errors' 16 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 17 import { 18 type Gif, ··· 31 import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' 32 import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' 33 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 34 + import {IS_WEB} from '#/env' 35 36 export function GifSelectDialog({ 37 controlRef, ··· 152 a.pb_sm, 153 t.atoms.bg, 154 ]}> 155 + {!gtMobile && IS_WEB && ( 156 <Button 157 size="small" 158 variant="ghost" ··· 164 </Button> 165 )} 166 167 + <TextField.Root style={[!gtMobile && IS_WEB && a.flex_1]}> 168 <TextField.Icon icon={Search} /> 169 <TextField.Input 170 label={_(msg`Search GIFs`)}
+2 -2
src/components/dialogs/InAppBrowserConsent.tsx
··· 4 import {useLingui} from '@lingui/react' 5 6 import {useOpenLink} from '#/lib/hooks/useOpenLink' 7 - import {isWeb} from '#/platform/detection' 8 import {useSetInAppBrowser} from '#/state/preferences/in-app-browser' 9 import {atoms as a, useTheme} from '#/alf' 10 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 11 import * as Dialog from '#/components/Dialog' 12 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as External} from '#/components/icons/SquareArrowTopRight' 13 import {Text} from '#/components/Typography' 14 import {useGlobalDialogsControlContext} from './Context' 15 16 export function InAppBrowserConsentDialog() { 17 const {inAppBrowserConsentControl} = useGlobalDialogsControlContext() 18 19 - if (isWeb) return null 20 21 return ( 22 <Dialog.Outer
··· 4 import {useLingui} from '@lingui/react' 5 6 import {useOpenLink} from '#/lib/hooks/useOpenLink' 7 import {useSetInAppBrowser} from '#/state/preferences/in-app-browser' 8 import {atoms as a, useTheme} from '#/alf' 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 import * as Dialog from '#/components/Dialog' 11 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as External} from '#/components/icons/SquareArrowTopRight' 12 import {Text} from '#/components/Typography' 13 + import {IS_WEB} from '#/env' 14 import {useGlobalDialogsControlContext} from './Context' 15 16 export function InAppBrowserConsentDialog() { 17 const {inAppBrowserConsentControl} = useGlobalDialogsControlContext() 18 19 + if (IS_WEB) return null 20 21 return ( 22 <Dialog.Outer
+2 -2
src/components/dialogs/MutedWords.tsx
··· 5 import {useLingui} from '@lingui/react' 6 7 import {logger} from '#/logger' 8 - import {isNative} from '#/platform/detection' 9 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 10 import { 11 usePreferencesQuery, ··· 33 import {Loader} from '#/components/Loader' 34 import * as Prompt from '#/components/Prompt' 35 import {Text} from '#/components/Typography' 36 37 const ONE_DAY = 24 * 60 * 60 * 1000 38 ··· 407 )} 408 </View> 409 410 - {isNative && <View style={{height: 20}} />} 411 </View> 412 413 <Dialog.Close />
··· 5 import {useLingui} from '@lingui/react' 6 7 import {logger} from '#/logger' 8 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 9 import { 10 usePreferencesQuery, ··· 32 import {Loader} from '#/components/Loader' 33 import * as Prompt from '#/components/Prompt' 34 import {Text} from '#/components/Typography' 35 + import {IS_NATIVE} from '#/env' 36 37 const ONE_DAY = 24 * 60 * 60 * 1000 38 ··· 407 )} 408 </View> 409 410 + {IS_NATIVE && <View style={{height: 20}} />} 411 </View> 412 413 <Dialog.Close />
+2 -2
src/components/dialogs/PostInteractionSettingsDialog.tsx
··· 12 import {useHaptics} from '#/lib/haptics' 13 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 14 import {logger} from '#/logger' 15 - import {isIOS} from '#/platform/detection' 16 import {STALE} from '#/state/queries' 17 import {useMyListsQuery} from '#/state/queries/my-lists' 18 import {useGetPost} from '#/state/queries/post' ··· 52 import {CloseQuote_Stroke2_Corner1_Rounded as QuoteIcon} from '#/components/icons/Quote' 53 import {Loader} from '#/components/Loader' 54 import {Text} from '#/components/Typography' 55 56 export type PostInteractionSettingsFormProps = { 57 canSave?: boolean ··· 531 hitSlop={0} 532 onPress={() => { 533 playHaptic('Light') 534 - if (isIOS && !showLists) { 535 LayoutAnimation.configureNext({ 536 ...LayoutAnimation.Presets.linear, 537 duration: 175,
··· 12 import {useHaptics} from '#/lib/haptics' 13 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 14 import {logger} from '#/logger' 15 import {STALE} from '#/state/queries' 16 import {useMyListsQuery} from '#/state/queries/my-lists' 17 import {useGetPost} from '#/state/queries/post' ··· 51 import {CloseQuote_Stroke2_Corner1_Rounded as QuoteIcon} from '#/components/icons/Quote' 52 import {Loader} from '#/components/Loader' 53 import {Text} from '#/components/Typography' 54 + import {IS_IOS} from '#/env' 55 56 export type PostInteractionSettingsFormProps = { 57 canSave?: boolean ··· 531 hitSlop={0} 532 onPress={() => { 533 playHaptic('Light') 534 + if (IS_IOS && !showLists) { 535 LayoutAnimation.configureNext({ 536 ...LayoutAnimation.Presets.linear, 537 duration: 175,
+4 -4
src/components/dialogs/SearchablePeopleList.tsx
··· 13 14 import {sanitizeDisplayName} from '#/lib/strings/display-names' 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 - import {isWeb} from '#/platform/detection' 17 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 18 import {useModerationOpts} from '#/state/preferences/moderation-opts' 19 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' ··· 30 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 31 import * as ProfileCard from '#/components/ProfileCard' 32 import {Text} from '#/components/Typography' 33 import type * as bsky from '#/types/bsky' 34 35 export type ProfileItem = { ··· 257 ) 258 259 useLayoutEffect(() => { 260 - if (isWeb) { 261 setImmediate(() => { 262 inputRef?.current?.focus() 263 }) ··· 293 ]}> 294 {title} 295 </Text> 296 - {isWeb ? ( 297 <Button 298 label={_(msg`Close`)} 299 size="small" 300 shape={enableSquareButtons ? 'square' : 'round'} 301 - variant={isWeb ? 'ghost' : 'solid'} 302 color="secondary" 303 style={[ 304 a.absolute,
··· 13 14 import {sanitizeDisplayName} from '#/lib/strings/display-names' 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 17 import {useModerationOpts} from '#/state/preferences/moderation-opts' 18 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' ··· 29 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 30 import * as ProfileCard from '#/components/ProfileCard' 31 import {Text} from '#/components/Typography' 32 + import {IS_WEB} from '#/env' 33 import type * as bsky from '#/types/bsky' 34 35 export type ProfileItem = { ··· 257 ) 258 259 useLayoutEffect(() => { 260 + if (IS_WEB) { 261 setImmediate(() => { 262 inputRef?.current?.focus() 263 }) ··· 293 ]}> 294 {title} 295 </Text> 296 + {IS_WEB ? ( 297 <Button 298 label={_(msg`Close`)} 299 size="small" 300 shape={enableSquareButtons ? 'square' : 'round'} 301 + variant={IS_WEB ? 'ghost' : 'solid'} 302 color="secondary" 303 style={[ 304 a.absolute,
+3 -3
src/components/dialogs/Signin.tsx
··· 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 - import {isNative} from '#/platform/detection' 7 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 8 import {useCloseAllActiveElements} from '#/state/util' 9 import {Logo} from '#/view/icons/Logo' ··· 13 import * as Dialog from '#/components/Dialog' 14 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 15 import {Text} from '#/components/Typography' 16 17 export function SigninDialog() { 18 const {signinDialogControl: control} = useGlobalDialogsControlContext() ··· 45 <Dialog.ScrollableInner 46 label={_(msg`Sign in to Bluesky or create a new account`)} 47 style={[gtMobile ? {width: 'auto', maxWidth: 420} : a.w_full]}> 48 - <View style={[!isNative && a.p_2xl]}> 49 <View 50 style={[ 51 a.flex_row, ··· 101 </Button> 102 </View> 103 104 - {isNative && <View style={{height: 10}} />} 105 </View> 106 107 <Dialog.Close />
··· 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 7 import {useCloseAllActiveElements} from '#/state/util' 8 import {Logo} from '#/view/icons/Logo' ··· 12 import * as Dialog from '#/components/Dialog' 13 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 14 import {Text} from '#/components/Typography' 15 + import {IS_NATIVE} from '#/env' 16 17 export function SigninDialog() { 18 const {signinDialogControl: control} = useGlobalDialogsControlContext() ··· 45 <Dialog.ScrollableInner 46 label={_(msg`Sign in to Bluesky or create a new account`)} 47 style={[gtMobile ? {width: 'auto', maxWidth: 420} : a.w_full]}> 48 + <View style={[!IS_NATIVE && a.p_2xl]}> 49 <View 50 style={[ 51 a.flex_row, ··· 101 </Button> 102 </View> 103 104 + {IS_NATIVE && <View style={{height: 10}} />} 105 </View> 106 107 <Dialog.Close />
+4 -4
src/components/dialogs/StarterPackDialog.tsx
··· 12 import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' 13 import {type NavigationProp} from '#/lib/routes/types' 14 import {logger} from '#/logger' 15 - import {isWeb} from '#/platform/detection' 16 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 17 import { 18 invalidateActorStarterPacksWithMembershipQuery, ··· 33 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 34 import {Loader} from '#/components/Loader' 35 import {Text} from '#/components/Typography' 36 import * as bsky from '#/types/bsky' 37 38 type StarterPackWithMembership = ··· 92 const t = useTheme() 93 94 return ( 95 - <View style={[a.gap_2xl, {paddingTop: isWeb ? 100 : 64}]}> 96 <View style={[a.gap_xs, a.align_center]}> 97 <StarterPack 98 width={48} ··· 172 <View 173 style={[ 174 {justifyContent: 'space-between', flexDirection: 'row'}, 175 - isWeb ? a.mb_2xl : a.my_lg, 176 a.align_center, 177 ]}> 178 <Text style={[a.text_lg, a.font_semi_bold]}> ··· 235 onEndReachedThreshold={0.1} 236 ListHeaderComponent={listHeader} 237 ListEmptyComponent={<Empty onStartWizard={onStartWizard} />} 238 - style={isWeb ? [a.px_md, {minHeight: 500}] : [a.px_2xl, a.pt_lg]} 239 /> 240 ) 241 }
··· 12 import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' 13 import {type NavigationProp} from '#/lib/routes/types' 14 import {logger} from '#/logger' 15 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 16 import { 17 invalidateActorStarterPacksWithMembershipQuery, ··· 32 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 33 import {Loader} from '#/components/Loader' 34 import {Text} from '#/components/Typography' 35 + import {IS_WEB} from '#/env' 36 import * as bsky from '#/types/bsky' 37 38 type StarterPackWithMembership = ··· 92 const t = useTheme() 93 94 return ( 95 + <View style={[a.gap_2xl, {paddingTop: IS_WEB ? 100 : 64}]}> 96 <View style={[a.gap_xs, a.align_center]}> 97 <StarterPack 98 width={48} ··· 172 <View 173 style={[ 174 {justifyContent: 'space-between', flexDirection: 'row'}, 175 + IS_WEB ? a.mb_2xl : a.my_lg, 176 a.align_center, 177 ]}> 178 <Text style={[a.text_lg, a.font_semi_bold]}> ··· 235 onEndReachedThreshold={0.1} 236 ListHeaderComponent={listHeader} 237 ListEmptyComponent={<Empty onStartWizard={onStartWizard} />} 238 + style={IS_WEB ? [a.px_md, {minHeight: 500}] : [a.px_2xl, a.pt_lg]} 239 /> 240 ) 241 }
+2 -2
src/components/dialogs/lists/CreateOrEditListDialog.tsx
··· 9 import {richTextToString} from '#/lib/strings/rich-text-helpers' 10 import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' 11 import {logger} from '#/logger' 12 - import {isWeb} from '#/platform/detection' 13 import {type ImageMeta} from '#/state/gallery' 14 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 15 import { ··· 27 import {Loader} from '#/components/Loader' 28 import * as Prompt from '#/components/Prompt' 29 import {Text} from '#/components/Typography' 30 31 const DISPLAY_NAME_MAX_GRAPHEMES = 64 32 const DESCRIPTION_MAX_GRAPHEMES = 300 ··· 49 50 // 'You might lose unsaved changes' warning 51 useEffect(() => { 52 - if (isWeb && dirty) { 53 const abortController = new AbortController() 54 const {signal} = abortController 55 window.addEventListener('beforeunload', evt => evt.preventDefault(), {
··· 9 import {richTextToString} from '#/lib/strings/rich-text-helpers' 10 import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' 11 import {logger} from '#/logger' 12 import {type ImageMeta} from '#/state/gallery' 13 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 14 import { ··· 26 import {Loader} from '#/components/Loader' 27 import * as Prompt from '#/components/Prompt' 28 import {Text} from '#/components/Typography' 29 + import {IS_WEB} from '#/env' 30 31 const DISPLAY_NAME_MAX_GRAPHEMES = 64 32 const DESCRIPTION_MAX_GRAPHEMES = 300 ··· 49 50 // 'You might lose unsaved changes' warning 51 useEffect(() => { 52 + if (IS_WEB && dirty) { 53 const abortController = new AbortController() 54 const {signal} = abortController 55 window.addEventListener('beforeunload', evt => evt.preventDefault(), {
+6 -6
src/components/dialogs/nuxs/ActivitySubscriptions.tsx
··· 4 import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 - import {isWeb} from '#/platform/detection' 8 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {Button, ButtonText} from '#/components/Button' ··· 12 import {useNuxDialogContext} from '#/components/dialogs/nuxs' 13 import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' 14 import {Text} from '#/components/Typography' 15 16 export function ActivitySubscriptionsNUX() { 17 const t = useTheme() ··· 47 a.overflow_hidden, 48 t.atoms.bg_contrast_25, 49 { 50 - gap: isWeb ? 16 : 24, 51 - paddingTop: isWeb ? 24 : 48, 52 borderTopLeftRadius: a.rounded_md.borderRadius, 53 borderTopRightRadius: a.rounded_md.borderRadius, 54 }, ··· 123 style={[ 124 a.align_center, 125 a.px_xl, 126 - isWeb ? [a.pt_xl, a.gap_xl, a.pb_sm] : [a.pt_3xl, a.gap_3xl], 127 ]}> 128 <View style={[a.gap_md, a.align_center]}> 129 <Text ··· 133 a.font_bold, 134 a.text_center, 135 { 136 - fontSize: isWeb ? 28 : 32, 137 maxWidth: 300, 138 }, 139 ]}> ··· 156 </Text> 157 </View> 158 159 - {!isWeb && ( 160 <Button 161 label={_(msg`Close`)} 162 size="large"
··· 4 import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 8 import {atoms as a, useTheme, web} from '#/alf' 9 import {Button, ButtonText} from '#/components/Button' ··· 11 import {useNuxDialogContext} from '#/components/dialogs/nuxs' 12 import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' 13 import {Text} from '#/components/Typography' 14 + import {IS_WEB} from '#/env' 15 16 export function ActivitySubscriptionsNUX() { 17 const t = useTheme() ··· 47 a.overflow_hidden, 48 t.atoms.bg_contrast_25, 49 { 50 + gap: IS_WEB ? 16 : 24, 51 + paddingTop: IS_WEB ? 24 : 48, 52 borderTopLeftRadius: a.rounded_md.borderRadius, 53 borderTopRightRadius: a.rounded_md.borderRadius, 54 }, ··· 123 style={[ 124 a.align_center, 125 a.px_xl, 126 + IS_WEB ? [a.pt_xl, a.gap_xl, a.pb_sm] : [a.pt_3xl, a.gap_3xl], 127 ]}> 128 <View style={[a.gap_md, a.align_center]}> 129 <Text ··· 133 a.font_bold, 134 a.text_center, 135 { 136 + fontSize: IS_WEB ? 28 : 32, 137 maxWidth: 300, 138 }, 139 ]}> ··· 156 </Text> 157 </View> 158 159 + {!IS_WEB && ( 160 <Button 161 label={_(msg`Close`)} 162 size="large"
+5 -5
src/components/dialogs/nuxs/BookmarksAnnouncement.tsx
··· 5 import {msg, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 8 - import {isWeb} from '#/platform/detection' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {transparentifyColor} from '#/alf/util/colorGeneration' 11 import {Button, ButtonText} from '#/components/Button' ··· 13 import {useNuxDialogContext} from '#/components/dialogs/nuxs' 14 import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' 15 import {Text} from '#/components/Typography' 16 17 export function BookmarksAnnouncement() { 18 const t = useTheme() ··· 49 a.overflow_hidden, 50 { 51 gap: 16, 52 - paddingTop: isWeb ? 24 : 40, 53 borderTopLeftRadius: a.rounded_md.borderRadius, 54 borderTopRightRadius: a.rounded_md.borderRadius, 55 }, ··· 90 borderRadius: 24, 91 aspectRatio: 333 / 104, 92 }, 93 - isWeb 94 ? [ 95 { 96 boxShadow: `0px 10px 15px -3px ${transparentifyColor(t.palette.black, 0.2)}`, ··· 136 a.font_bold, 137 a.text_center, 138 { 139 - fontSize: isWeb ? 28 : 32, 140 maxWidth: 300, 141 }, 142 ]}> ··· 158 </Text> 159 </View> 160 161 - {!isWeb && ( 162 <Button 163 label={_(msg`Close`)} 164 size="large"
··· 5 import {msg, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 8 import {atoms as a, useTheme, web} from '#/alf' 9 import {transparentifyColor} from '#/alf/util/colorGeneration' 10 import {Button, ButtonText} from '#/components/Button' ··· 12 import {useNuxDialogContext} from '#/components/dialogs/nuxs' 13 import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' 14 import {Text} from '#/components/Typography' 15 + import {IS_WEB} from '#/env' 16 17 export function BookmarksAnnouncement() { 18 const t = useTheme() ··· 49 a.overflow_hidden, 50 { 51 gap: 16, 52 + paddingTop: IS_WEB ? 24 : 40, 53 borderTopLeftRadius: a.rounded_md.borderRadius, 54 borderTopRightRadius: a.rounded_md.borderRadius, 55 }, ··· 90 borderRadius: 24, 91 aspectRatio: 333 / 104, 92 }, 93 + IS_WEB 94 ? [ 95 { 96 boxShadow: `0px 10px 15px -3px ${transparentifyColor(t.palette.black, 0.2)}`, ··· 136 a.font_bold, 137 a.text_center, 138 { 139 + fontSize: IS_WEB ? 28 : 32, 140 maxWidth: 300, 141 }, 142 ]}> ··· 158 </Text> 159 </View> 160 161 + {!IS_WEB && ( 162 <Button 163 label={_(msg`Close`)} 164 size="large"
+3 -3
src/components/dialogs/nuxs/FindContactsAnnouncement.tsx
··· 6 import {useLingui} from '@lingui/react' 7 8 import {logger} from '#/logger' 9 - import {isNative, isWeb} from '#/platform/detection' 10 import {atoms as a, useTheme, web} from '#/alf' 11 import {Button, ButtonText} from '#/components/Button' 12 import {isFindContactsFeatureEnabled} from '#/components/contacts/country-allowlist' ··· 17 isExistingUserAsOf, 18 } from '#/components/dialogs/nuxs/utils' 19 import {Text} from '#/components/Typography' 20 import {IS_E2E} from '#/env' 21 import {navigate} from '#/Navigation' 22 23 export const enabled = createIsEnabledCheck(props => { 24 return ( 25 !IS_E2E && 26 - isNative && 27 isExistingUserAsOf( 28 '2025-12-16T00:00:00.000Z', 29 props.currentProfile.createdAt, ··· 89 a.font_bold, 90 a.text_center, 91 { 92 - fontSize: isWeb ? 28 : 32, 93 maxWidth: 300, 94 }, 95 ]}>
··· 6 import {useLingui} from '@lingui/react' 7 8 import {logger} from '#/logger' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {Button, ButtonText} from '#/components/Button' 11 import {isFindContactsFeatureEnabled} from '#/components/contacts/country-allowlist' ··· 16 isExistingUserAsOf, 17 } from '#/components/dialogs/nuxs/utils' 18 import {Text} from '#/components/Typography' 19 + import {IS_NATIVE, IS_WEB} from '#/env' 20 import {IS_E2E} from '#/env' 21 import {navigate} from '#/Navigation' 22 23 export const enabled = createIsEnabledCheck(props => { 24 return ( 25 !IS_E2E && 26 + IS_NATIVE && 27 isExistingUserAsOf( 28 '2025-12-16T00:00:00.000Z', 29 props.currentProfile.createdAt, ··· 89 a.font_bold, 90 a.text_center, 91 { 92 + fontSize: IS_WEB ? 28 : 32, 93 maxWidth: 300, 94 }, 95 ]}>
+2 -2
src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx
··· 6 7 import {urls} from '#/lib/constants' 8 import {logger} from '#/logger' 9 - import {isNative} from '#/platform/detection' 10 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 12 import {Button, ButtonText} from '#/components/Button' ··· 16 import {VerifierCheck} from '#/components/icons/VerifierCheck' 17 import {Link} from '#/components/Link' 18 import {Span, Text} from '#/components/Typography' 19 20 export function InitialVerificationAnnouncement() { 21 const t = useTheme() ··· 176 <Trans>Read blog post</Trans> 177 </ButtonText> 178 </Link> 179 - {isNative && ( 180 <Button 181 label={_(msg`Close`)} 182 size="small"
··· 6 7 import {urls} from '#/lib/constants' 8 import {logger} from '#/logger' 9 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 10 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 11 import {Button, ButtonText} from '#/components/Button' ··· 15 import {VerifierCheck} from '#/components/icons/VerifierCheck' 16 import {Link} from '#/components/Link' 17 import {Span, Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 20 export function InitialVerificationAnnouncement() { 21 const t = useTheme() ··· 176 <Trans>Read blog post</Trans> 177 </ButtonText> 178 </Link> 179 + {IS_NATIVE && ( 180 <Button 181 label={_(msg`Close`)} 182 size="small"
+6 -6
src/components/dialogs/nuxs/LiveNowBetaDialog.tsx
··· 5 import {msg, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 8 - import {isWeb} from '#/platform/detection' 9 import {atoms as a, select, useTheme, utils, web} from '#/alf' 10 import {Button, ButtonText} from '#/components/Button' 11 import * as Dialog from '#/components/Dialog' ··· 16 } from '#/components/dialogs/nuxs/utils' 17 import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker' 18 import {Text} from '#/components/Typography' 19 import {IS_E2E} from '#/env' 20 21 export const enabled = createIsEnabledCheck(props => { ··· 25 '2026-01-16T00:00:00.000Z', 26 props.currentProfile.createdAt, 27 ) && 28 - props.gate('live_now_beta') 29 ) 30 }) 31 ··· 72 a.overflow_hidden, 73 { 74 gap: 16, 75 - paddingTop: isWeb ? 24 : 40, 76 borderTopLeftRadius: a.rounded_md.borderRadius, 77 borderTopRightRadius: a.rounded_md.borderRadius, 78 }, ··· 116 borderRadius: 24, 117 aspectRatio: 652 / 211, 118 }, 119 - isWeb 120 ? [ 121 { 122 boxShadow: `0px 10px 15px -3px ${shadowColor}`, ··· 163 a.font_bold, 164 a.text_center, 165 { 166 - fontSize: isWeb ? 28 : 32, 167 maxWidth: 360, 168 }, 169 ]}> ··· 186 </Text> 187 </View> 188 189 - {!isWeb && ( 190 <Button 191 label={_(msg`Close`)} 192 size="large"
··· 5 import {msg, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 8 import {atoms as a, select, useTheme, utils, web} from '#/alf' 9 import {Button, ButtonText} from '#/components/Button' 10 import * as Dialog from '#/components/Dialog' ··· 15 } from '#/components/dialogs/nuxs/utils' 16 import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker' 17 import {Text} from '#/components/Typography' 18 + import {IS_WEB} from '#/env' 19 import {IS_E2E} from '#/env' 20 21 export const enabled = createIsEnabledCheck(props => { ··· 25 '2026-01-16T00:00:00.000Z', 26 props.currentProfile.createdAt, 27 ) && 28 + !props.gate('disable_live_now_beta') 29 ) 30 }) 31 ··· 72 a.overflow_hidden, 73 { 74 gap: 16, 75 + paddingTop: IS_WEB ? 24 : 40, 76 borderTopLeftRadius: a.rounded_md.borderRadius, 77 borderTopRightRadius: a.rounded_md.borderRadius, 78 }, ··· 116 borderRadius: 24, 117 aspectRatio: 652 / 211, 118 }, 119 + IS_WEB 120 ? [ 121 { 122 boxShadow: `0px 10px 15px -3px ${shadowColor}`, ··· 163 a.font_bold, 164 a.text_center, 165 { 166 + fontSize: IS_WEB ? 28 : 32, 167 maxWidth: 360, 168 }, 169 ]}> ··· 186 </Text> 187 </View> 188 189 + {!IS_WEB && ( 190 <Button 191 label={_(msg`Close`)} 192 size="large"
+1 -1
src/components/dms/ActionsWrapper.tsx
··· 21 <MessageContextMenu message={message}> 22 {trigger => 23 // will always be true, since this file is platform split 24 - trigger.isNative && ( 25 <View style={[a.flex_1, a.relative]}> 26 <View 27 style={[
··· 21 <MessageContextMenu message={message}> 22 {trigger => 23 // will always be true, since this file is platform split 24 + trigger.IS_NATIVE && ( 25 <View style={[a.flex_1, a.relative]}> 26 <View 27 style={[
+4 -4
src/components/dms/ActionsWrapper.web.tsx
··· 92 : [a.ml_xs, {marginRight: 'auto'}], 93 ]}> 94 <EmojiReactionPicker message={message} onEmojiSelect={onEmojiSelect}> 95 - {({props, state, isNative, control}) => { 96 // always false, file is platform split 97 - if (isNative) return null 98 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 99 return ( 100 <Pressable ··· 114 }} 115 </EmojiReactionPicker> 116 <MessageContextMenu message={message}> 117 - {({props, state, isNative, control}) => { 118 // always false, file is platform split 119 - if (isNative) return null 120 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 121 return ( 122 <Pressable
··· 92 : [a.ml_xs, {marginRight: 'auto'}], 93 ]}> 94 <EmojiReactionPicker message={message} onEmojiSelect={onEmojiSelect}> 95 + {({props, state, IS_NATIVE, control}) => { 96 // always false, file is platform split 97 + if (IS_NATIVE) return null 98 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 99 return ( 100 <Pressable ··· 114 }} 115 </EmojiReactionPicker> 116 <MessageContextMenu message={message}> 117 + {({props, state, IS_NATIVE, control}) => { 118 // always false, file is platform split 119 + if (IS_NATIVE) return null 120 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 121 return ( 122 <Pressable
+2 -2
src/components/dms/AfterReportDialog.tsx
··· 7 import type React from 'react' 8 9 import {type NavigationProp} from '#/lib/routes/types' 10 - import {isNative} from '#/platform/detection' 11 import {useProfileShadow} from '#/state/cache/profile-shadow' 12 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 13 import { ··· 21 import * as Toggle from '#/components/forms/Toggle' 22 import {Loader} from '#/components/Loader' 23 import {Text} from '#/components/Typography' 24 25 type ReportDialogParams = { 26 convoId: string ··· 130 onMutate: () => { 131 if (currentScreen === 'conversation') { 132 navigation.dispatch( 133 - StackActions.replace('Messages', isNative ? {animation: 'pop'} : {}), 134 ) 135 } 136 },
··· 7 import type React from 'react' 8 9 import {type NavigationProp} from '#/lib/routes/types' 10 import {useProfileShadow} from '#/state/cache/profile-shadow' 11 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 12 import { ··· 20 import * as Toggle from '#/components/forms/Toggle' 21 import {Loader} from '#/components/Loader' 22 import {Text} from '#/components/Typography' 23 + import {IS_NATIVE} from '#/env' 24 25 type ReportDialogParams = { 26 convoId: string ··· 130 onMutate: () => { 131 if (currentScreen === 'conversation') { 132 navigation.dispatch( 133 + StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}), 134 ) 135 } 136 },
+3 -3
src/components/dms/ChatEmptyPill.tsx
··· 12 import {ScaleAndFadeIn} from '#/lib/custom-animations/ScaleAndFade' 13 import {ShrinkAndPop} from '#/lib/custom-animations/ShrinkAndPop' 14 import {useHaptics} from '#/lib/haptics' 15 - import {isWeb} from '#/platform/detection' 16 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 17 import {atoms as a, useTheme} from '#/alf' 18 import {Text} from '#/components/Typography' 19 20 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 21 ··· 44 }, [_]) 45 46 const onPressIn = React.useCallback(() => { 47 - if (isWeb) return 48 scale.set(() => withTiming(1.075, {duration: 100})) 49 }, [scale]) 50 51 const onPressOut = React.useCallback(() => { 52 - if (isWeb) return 53 scale.set(() => withTiming(1, {duration: 100})) 54 }, [scale]) 55
··· 12 import {ScaleAndFadeIn} from '#/lib/custom-animations/ScaleAndFade' 13 import {ShrinkAndPop} from '#/lib/custom-animations/ShrinkAndPop' 14 import {useHaptics} from '#/lib/haptics' 15 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 16 import {atoms as a, useTheme} from '#/alf' 17 import {Text} from '#/components/Typography' 18 + import {IS_WEB} from '#/env' 19 20 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 21 ··· 44 }, [_]) 45 46 const onPressIn = React.useCallback(() => { 47 + if (IS_WEB) return 48 scale.set(() => withTiming(1.075, {duration: 100})) 49 }, [scale]) 50 51 const onPressOut = React.useCallback(() => { 52 + if (IS_WEB) return 53 scale.set(() => withTiming(1, {duration: 100})) 54 }, [scale]) 55
+2 -2
src/components/dms/LeaveConvoPrompt.tsx
··· 3 import {StackActions, useNavigation} from '@react-navigation/native' 4 5 import {type NavigationProp} from '#/lib/routes/types' 6 - import {isNative} from '#/platform/detection' 7 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 8 import * as Toast from '#/view/com/util/Toast' 9 import {type DialogOuterProps} from '#/components/Dialog' 10 import * as Prompt from '#/components/Prompt' 11 12 export function LeaveConvoPrompt({ 13 control, ··· 27 onMutate: () => { 28 if (currentScreen === 'conversation') { 29 navigation.dispatch( 30 - StackActions.replace('Messages', isNative ? {animation: 'pop'} : {}), 31 ) 32 } 33 },
··· 3 import {StackActions, useNavigation} from '@react-navigation/native' 4 5 import {type NavigationProp} from '#/lib/routes/types' 6 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 7 import * as Toast from '#/view/com/util/Toast' 8 import {type DialogOuterProps} from '#/components/Dialog' 9 import * as Prompt from '#/components/Prompt' 10 + import {IS_NATIVE} from '#/env' 11 12 export function LeaveConvoPrompt({ 13 control, ··· 27 onMutate: () => { 28 if (currentScreen === 'conversation') { 29 navigation.dispatch( 30 + StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}), 31 ) 32 } 33 },
+2 -2
src/components/dms/MessageContextMenu.tsx
··· 8 import {useTranslate} from '#/lib/hooks/useTranslate' 9 import {richTextToString} from '#/lib/strings/rich-text-helpers' 10 import {logger} from '#/logger' 11 - import {isNative} from '#/platform/detection' 12 import {useConvoActive} from '#/state/messages/convo' 13 import {useLanguagePrefs} from '#/state/preferences' 14 import {useSession} from '#/state/session' ··· 23 import {ReportDialog} from '#/components/moderation/ReportDialog' 24 import * as Prompt from '#/components/Prompt' 25 import {usePromptControl} from '#/components/Prompt' 26 import {EmojiReactionPicker} from './EmojiReactionPicker' 27 import {hasReachedReactionLimit} from './util' 28 ··· 112 return ( 113 <> 114 <ContextMenu.Root> 115 - {isNative && ( 116 <ContextMenu.AuxiliaryView align={isFromSelf ? 'right' : 'left'}> 117 <EmojiReactionPicker 118 message={message}
··· 8 import {useTranslate} from '#/lib/hooks/useTranslate' 9 import {richTextToString} from '#/lib/strings/rich-text-helpers' 10 import {logger} from '#/logger' 11 import {useConvoActive} from '#/state/messages/convo' 12 import {useLanguagePrefs} from '#/state/preferences' 13 import {useSession} from '#/state/session' ··· 22 import {ReportDialog} from '#/components/moderation/ReportDialog' 23 import * as Prompt from '#/components/Prompt' 24 import {usePromptControl} from '#/components/Prompt' 25 + import {IS_NATIVE} from '#/env' 26 import {EmojiReactionPicker} from './EmojiReactionPicker' 27 import {hasReachedReactionLimit} from './util' 28 ··· 112 return ( 113 <> 114 <ContextMenu.Root> 115 + {IS_NATIVE && ( 116 <ContextMenu.AuxiliaryView align={isFromSelf ? 'right' : 'left'}> 117 <EmojiReactionPicker 118 message={message}
+3 -3
src/components/dms/MessageItem.tsx
··· 21 import {useLingui} from '@lingui/react' 22 23 import {sanitizeDisplayName} from '#/lib/strings/display-names' 24 - import {isNative} from '#/platform/detection' 25 import {useConvoActive} from '#/state/messages/convo' 26 import {type ConvoItem} from '#/state/messages/convo/types' 27 import {useSession} from '#/state/session' ··· 32 import {InlineLinkText} from '#/components/Link' 33 import {RichText} from '#/components/RichText' 34 import {Text} from '#/components/Typography' 35 import {DateDivider} from './DateDivider' 36 import {MessageItemEmbed} from './MessageItemEmbed' 37 import {localDateString} from './util' ··· 218 </View> 219 )} 220 221 - {isNative && appliedReactions} 222 </ActionsWrapper> 223 224 - {!isNative && appliedReactions} 225 226 {isLastInGroup && ( 227 <MessageItemMetadata
··· 21 import {useLingui} from '@lingui/react' 22 23 import {sanitizeDisplayName} from '#/lib/strings/display-names' 24 import {useConvoActive} from '#/state/messages/convo' 25 import {type ConvoItem} from '#/state/messages/convo/types' 26 import {useSession} from '#/state/session' ··· 31 import {InlineLinkText} from '#/components/Link' 32 import {RichText} from '#/components/RichText' 33 import {Text} from '#/components/Typography' 34 + import {IS_NATIVE} from '#/env' 35 import {DateDivider} from './DateDivider' 36 import {MessageItemEmbed} from './MessageItemEmbed' 37 import {localDateString} from './util' ··· 218 </View> 219 )} 220 221 + {IS_NATIVE && appliedReactions} 222 </ActionsWrapper> 223 224 + {!IS_NATIVE && appliedReactions} 225 226 {isLastInGroup && ( 227 <MessageItemMetadata
+2 -2
src/components/dms/MessagesListHeader.tsx
··· 10 11 import {makeProfileLink} from '#/lib/routes/links' 12 import {sanitizeDisplayName} from '#/lib/strings/display-names' 13 - import {isWeb} from '#/platform/detection' 14 import {type Shadow} from '#/state/cache/profile-shadow' 15 import {isConvoActive, useConvo} from '#/state/messages/convo' 16 import {type ConvoItem} from '#/state/messages/convo/types' ··· 24 import {Text} from '#/components/Typography' 25 import {useSimpleVerificationState} from '#/components/verification' 26 import {VerificationCheck} from '#/components/verification/VerificationCheck' 27 28 - const PFP_SIZE = isWeb ? 40 : Layout.HEADER_SLOT_SIZE 29 30 export function MessagesListHeader({ 31 profile,
··· 10 11 import {makeProfileLink} from '#/lib/routes/links' 12 import {sanitizeDisplayName} from '#/lib/strings/display-names' 13 import {type Shadow} from '#/state/cache/profile-shadow' 14 import {isConvoActive, useConvo} from '#/state/messages/convo' 15 import {type ConvoItem} from '#/state/messages/convo/types' ··· 23 import {Text} from '#/components/Typography' 24 import {useSimpleVerificationState} from '#/components/verification' 25 import {VerificationCheck} from '#/components/verification/VerificationCheck' 26 + import {IS_WEB} from '#/env' 27 28 + const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE 29 30 export function MessagesListHeader({ 31 profile,
+5 -5
src/components/dms/NewMessagesPill.tsx
··· 14 ScaleAndFadeOut, 15 } from '#/lib/custom-animations/ScaleAndFade' 16 import {useHaptics} from '#/lib/haptics' 17 - import {isAndroid, isIOS, isWeb} from '#/platform/detection' 18 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 19 import {atoms as a, useTheme} from '#/alf' 20 import {Text} from '#/components/Typography' 21 22 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 23 ··· 29 const t = useTheme() 30 const playHaptic = useHaptics() 31 const {bottom: bottomInset} = useSafeAreaInsets() 32 - const bottomBarHeight = isIOS ? 42 : isAndroid ? 60 : 0 33 - const bottomOffset = isWeb ? 0 : bottomInset + bottomBarHeight 34 35 const scale = useSharedValue(1) 36 37 const enableSquareButtons = useEnableSquareButtons() 38 39 const onPressIn = React.useCallback(() => { 40 - if (isWeb) return 41 scale.set(() => withTiming(1.075, {duration: 100})) 42 }, [scale]) 43 44 const onPressOut = React.useCallback(() => { 45 - if (isWeb) return 46 scale.set(() => withTiming(1, {duration: 100})) 47 }, [scale]) 48
··· 14 ScaleAndFadeOut, 15 } from '#/lib/custom-animations/ScaleAndFade' 16 import {useHaptics} from '#/lib/haptics' 17 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 18 import {atoms as a, useTheme} from '#/alf' 19 import {Text} from '#/components/Typography' 20 + import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env' 21 22 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 23 ··· 29 const t = useTheme() 30 const playHaptic = useHaptics() 31 const {bottom: bottomInset} = useSafeAreaInsets() 32 + const bottomBarHeight = IS_IOS ? 42 : IS_ANDROID ? 60 : 0 33 + const bottomOffset = IS_WEB ? 0 : bottomInset + bottomBarHeight 34 35 const scale = useSharedValue(1) 36 37 const enableSquareButtons = useEnableSquareButtons() 38 39 const onPressIn = React.useCallback(() => { 40 + if (IS_WEB) return 41 scale.set(() => withTiming(1.075, {duration: 100})) 42 }, [scale]) 43 44 const onPressOut = React.useCallback(() => { 45 + if (IS_WEB) return 46 scale.set(() => withTiming(1, {duration: 100})) 47 }, [scale]) 48
+2 -2
src/components/forms/SearchInput.tsx
··· 4 import {useLingui} from '@lingui/react' 5 6 import {HITSLOP_10} from '#/lib/constants' 7 - import {isNative} from '#/platform/detection' 8 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 9 import {atoms as a, useTheme} from '#/alf' 10 import {Button, ButtonIcon} from '#/components/Button' 11 import * as TextField from '#/components/forms/TextField' 12 import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass' 13 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 14 15 type SearchInputProps = Omit<TextField.InputProps, 'label'> & { 16 label?: TextField.InputProps['label'] ··· 39 placeholder={_(msg`Search`)} 40 returnKeyType="search" 41 keyboardAppearance={t.scheme} 42 - selectTextOnFocus={isNative} 43 autoFocus={false} 44 accessibilityRole="search" 45 autoCorrect={false}
··· 4 import {useLingui} from '@lingui/react' 5 6 import {HITSLOP_10} from '#/lib/constants' 7 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 8 import {atoms as a, useTheme} from '#/alf' 9 import {Button, ButtonIcon} from '#/components/Button' 10 import * as TextField from '#/components/forms/TextField' 11 import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass' 12 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 13 + import {IS_NATIVE} from '#/env' 14 15 type SearchInputProps = Omit<TextField.InputProps, 'label'> & { 16 label?: TextField.InputProps['label'] ··· 39 placeholder={_(msg`Search`)} 40 returnKeyType="search" 41 keyboardAppearance={t.scheme} 42 + selectTextOnFocus={IS_NATIVE} 43 autoFocus={false} 44 accessibilityRole="search" 45 autoCorrect={false}
+2 -2
src/components/forms/TextField.tsx
··· 11 12 import {HITSLOP_20} from '#/lib/constants' 13 import {mergeRefs} from '#/lib/merge-refs' 14 - import {isWeb} from '#/platform/detection' 15 import { 16 android, 17 applyFonts, ··· 26 import {useInteractionState} from '#/components/hooks/useInteractionState' 27 import {type Props as SVGIconProps} from '#/components/icons/common' 28 import {Text} from '#/components/Typography' 29 30 const Context = createContext<{ 31 inputRef: React.RefObject<TextInput | null> | null ··· 106 a.align_center, 107 a.relative, 108 a.w_full, 109 - !(hasMultiline && isWeb) && a.px_md, 110 style, 111 ]} 112 {...web({
··· 11 12 import {HITSLOP_20} from '#/lib/constants' 13 import {mergeRefs} from '#/lib/merge-refs' 14 import { 15 android, 16 applyFonts, ··· 25 import {useInteractionState} from '#/components/hooks/useInteractionState' 26 import {type Props as SVGIconProps} from '#/components/icons/common' 27 import {Text} from '#/components/Typography' 28 + import {IS_WEB} from '#/env/index.web' 29 30 const Context = createContext<{ 31 inputRef: React.RefObject<TextInput | null> | null ··· 106 a.align_center, 107 a.relative, 108 a.w_full, 109 + !(hasMultiline && IS_WEB) && a.px_md, 110 style, 111 ]} 112 {...web({
+2 -2
src/components/forms/Toggle/index.tsx
··· 10 11 import {HITSLOP_10} from '#/lib/constants' 12 import {useHaptics} from '#/lib/haptics' 13 - import {isNative} from '#/platform/detection' 14 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 15 import { 16 atoms as a, ··· 23 import {useInteractionState} from '#/components/hooks/useInteractionState' 24 import {CheckThick_Stroke2_Corner0_Rounded as Checkmark} from '#/components/icons/Check' 25 import {Text} from '#/components/Typography' 26 27 export * from './Panel' 28 ··· 564 ) 565 } 566 567 - export const Platform = isNative ? Switch : Checkbox
··· 10 11 import {HITSLOP_10} from '#/lib/constants' 12 import {useHaptics} from '#/lib/haptics' 13 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 14 import { 15 atoms as a, ··· 22 import {useInteractionState} from '#/components/hooks/useInteractionState' 23 import {CheckThick_Stroke2_Corner0_Rounded as Checkmark} from '#/components/icons/Check' 24 import {Text} from '#/components/Typography' 25 + import {IS_NATIVE} from '#/env' 26 27 export * from './Panel' 28 ··· 564 ) 565 } 566 567 + export const Platform = IS_NATIVE ? Switch : Checkbox
+3 -4
src/components/hooks/useFullscreen.ts
··· 6 useSyncExternalStore, 7 } from 'react' 8 9 - import {isFirefox, isSafari} from '#/lib/browser' 10 - import {isWeb} from '#/platform/detection' 11 12 function fullscreenSubscribe(onChange: () => void) { 13 document.addEventListener('fullscreenchange', onChange) ··· 15 } 16 17 export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) { 18 - if (!isWeb) throw new Error("'useFullscreen' is a web-only hook") 19 const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => 20 Boolean(document.fullscreenElement), 21 ) ··· 39 40 // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen 41 // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium 42 - if (prevIsFullscreen && !isFirefox && !isSafari) { 43 setTimeout(() => { 44 if (scrollYRef.current !== null) { 45 window.scrollTo(0, scrollYRef.current)
··· 6 useSyncExternalStore, 7 } from 'react' 8 9 + import {IS_WEB, IS_WEB_FIREFOX, IS_WEB_SAFARI} from '#/env' 10 11 function fullscreenSubscribe(onChange: () => void) { 12 document.addEventListener('fullscreenchange', onChange) ··· 14 } 15 16 export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) { 17 + if (!IS_WEB) throw new Error("'useFullscreen' is a web-only hook") 18 const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => 19 Boolean(document.fullscreenElement), 20 ) ··· 38 39 // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen 40 // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium 41 + if (prevIsFullscreen && !IS_WEB_FIREFOX && !IS_WEB_SAFARI) { 42 setTimeout(() => { 43 if (scrollYRef.current !== null) { 44 window.scrollTo(0, scrollYRef.current)
+2 -2
src/components/hooks/useStarterPackEntry.native.ts
··· 4 createStarterPackLinkFromAndroidReferrer, 5 httpStarterPackUriToAtUri, 6 } from '#/lib/strings/starter-pack' 7 - import {isAndroid} from '#/platform/detection' 8 import {useHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 9 import {useSetActiveStarterPack} from '#/state/shell/starter-pack' 10 import {Referrer, SharedPrefs} from '../../../modules/expo-bluesky-swiss-army' 11 12 export function useStarterPackEntry() { ··· 32 ;(async () => { 33 let uri: string | null | undefined 34 35 - if (isAndroid) { 36 const res = await Referrer.getGooglePlayReferrerInfoAsync() 37 38 if (res && res.installReferrer) {
··· 4 createStarterPackLinkFromAndroidReferrer, 5 httpStarterPackUriToAtUri, 6 } from '#/lib/strings/starter-pack' 7 import {useHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 8 import {useSetActiveStarterPack} from '#/state/shell/starter-pack' 9 + import {IS_ANDROID} from '#/env' 10 import {Referrer, SharedPrefs} from '../../../modules/expo-bluesky-swiss-army' 11 12 export function useStarterPackEntry() { ··· 32 ;(async () => { 33 let uri: string | null | undefined 34 35 + if (IS_ANDROID) { 36 const res = await Referrer.getGooglePlayReferrerInfoAsync() 37 38 if (res && res.installReferrer) {
+2 -2
src/components/hooks/useWelcomeModal.ts
··· 1 import {useEffect, useState} from 'react' 2 3 - import {isWeb} from '#/platform/detection' 4 import {useSession} from '#/state/session' 5 6 export function useWelcomeModal() { 7 const {hasSession} = useSession() ··· 22 // 2. We're on the web (this is a web-only feature) 23 // 3. We're on the homepage (path is '/' or '/home') 24 // 4. User hasn't actively closed the modal in this session 25 - if (isWeb && !hasSession && typeof window !== 'undefined') { 26 const currentPath = window.location.pathname 27 const isHomePage = currentPath === '/' 28 const hasUserClosedModal =
··· 1 import {useEffect, useState} from 'react' 2 3 import {useSession} from '#/state/session' 4 + import {IS_WEB} from '#/env' 5 6 export function useWelcomeModal() { 7 const {hasSession} = useSession() ··· 22 // 2. We're on the web (this is a web-only feature) 23 // 3. We're on the homepage (path is '/' or '/home') 24 // 4. User hasn't actively closed the modal in this session 25 + if (IS_WEB && !hasSession && typeof window !== 'undefined') { 26 const currentPath = window.location.pathname 27 const isHomePage = currentPath === '/' 28 const hasUserClosedModal =
+2 -2
src/components/images/AutoSizedImage.tsx
··· 11 import {useLingui} from '@lingui/react' 12 13 import {type Dimensions} from '#/lib/media/types' 14 - import {isNative} from '#/platform/detection' 15 import { 16 maybeModifyHighQualityImage, 17 useHighQualityImages, ··· 21 import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal' 22 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 23 import {Text} from '#/components/Typography' 24 25 export function ConstrainedImage({ 26 aspectRatio, ··· 39 * the height of the image. 40 */ 41 const outerAspectRatio = useMemo<DimensionValue>(() => { 42 - const ratio = isNative 43 ? Math.min(1 / aspectRatio, minMobileAspectRatio ?? 16 / 9) // 9:16 bounding box 44 : Math.min(1 / aspectRatio, 1) // 1:1 bounding box 45 return `${ratio * 100}%`
··· 11 import {useLingui} from '@lingui/react' 12 13 import {type Dimensions} from '#/lib/media/types' 14 import { 15 maybeModifyHighQualityImage, 16 useHighQualityImages, ··· 20 import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal' 21 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 22 import {Text} from '#/components/Typography' 23 + import {IS_NATIVE} from '#/env' 24 25 export function ConstrainedImage({ 26 aspectRatio, ··· 39 * the height of the image. 40 */ 41 const outerAspectRatio = useMemo<DimensionValue>(() => { 42 + const ratio = IS_NATIVE 43 ? Math.min(1 / aspectRatio, minMobileAspectRatio ?? 16 / 9) // 9:16 bounding box 44 : Math.min(1 / aspectRatio, 1) // 1:1 bounding box 45 return `${ratio * 100}%`
+3 -3
src/components/intents/VerifyEmailIntentDialog.tsx
··· 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 - import {isNative} from '#/platform/detection' 7 import {useAgent, useSession} from '#/state/session' 8 import {pdsAgent} from '#/state/session/agent' 9 import {atoms as a, useBreakpoints, useTheme} from '#/alf' ··· 16 import {useIntentDialogs} from '#/components/intents/IntentDialogs' 17 import {Loader} from '#/components/Loader' 18 import {Text} from '#/components/Typography' 19 20 export function VerifyEmailIntentDialog() { 21 const {verifyEmailDialogControl: control} = useIntentDialogs() ··· 69 <Loader size="xl" fill={t.atoms.text_contrast_low.color} /> 70 </View> 71 ) : status === 'success' ? ( 72 - <View style={[a.gap_sm, isNative && a.pb_xl]}> 73 <Text style={[a.font_bold, a.text_2xl]}> 74 <Trans>Email Verified</Trans> 75 </Text> ··· 94 </Text> 95 </View> 96 ) : ( 97 - <View style={[a.gap_sm, isNative && a.pb_xl]}> 98 <Text style={[a.font_bold, a.text_2xl]}> 99 <Trans>Email Resent</Trans> 100 </Text>
··· 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import {useAgent, useSession} from '#/state/session' 7 import {pdsAgent} from '#/state/session/agent' 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' ··· 15 import {useIntentDialogs} from '#/components/intents/IntentDialogs' 16 import {Loader} from '#/components/Loader' 17 import {Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 20 export function VerifyEmailIntentDialog() { 21 const {verifyEmailDialogControl: control} = useIntentDialogs() ··· 69 <Loader size="xl" fill={t.atoms.text_contrast_low.color} /> 70 </View> 71 ) : status === 'success' ? ( 72 + <View style={[a.gap_sm, IS_NATIVE && a.pb_xl]}> 73 <Text style={[a.font_bold, a.text_2xl]}> 74 <Trans>Email Verified</Trans> 75 </Text> ··· 94 </Text> 95 </View> 96 ) : ( 97 + <View style={[a.gap_sm, IS_NATIVE && a.pb_xl]}> 98 <Text style={[a.font_bold, a.text_2xl]}> 99 <Trans>Email Resent</Trans> 100 </Text>
+1 -1
src/components/live/EditLiveDialog.tsx
··· 98 } = useRemoveLiveStatusMutation() 99 100 const {minutesUntilExpiry, expiryDateTime} = useMemo(() => { 101 - tick! 102 103 const expiry = new Date(status.expiresAt ?? new Date()) 104 return {
··· 98 } = useRemoveLiveStatusMutation() 99 100 const {minutesUntilExpiry, expiryDateTime} = useMemo(() => { 101 + void tick 102 103 const expiry = new Date(status.expiresAt ?? new Date()) 104 return {
+6 -3
src/components/live/GoLiveDialog.tsx
··· 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 import * as Dialog from '#/components/Dialog' 15 import * as TextField from '#/components/forms/TextField' 16 - import {getLiveServiceNames} from '#/components/live/utils' 17 import {Loader} from '#/components/Loader' 18 import * as ProfileCard from '#/components/ProfileCard' 19 import * as Select from '#/components/Select' ··· 21 import type * as bsky from '#/types/bsky' 22 import {LinkPreview} from './LinkPreview' 23 import {useLiveLinkMetaQuery, useUpsertLiveStatusMutation} from './queries' 24 - import {displayDuration, useDebouncedValue} from './utils' 25 26 export function GoLiveDialog({ 27 control, ··· 57 58 const time = useCallback( 59 (offset: number) => { 60 - tick! 61 62 const date = new Date() 63 date.setMinutes(date.getMinutes() + offset)
··· 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 import * as Dialog from '#/components/Dialog' 15 import * as TextField from '#/components/forms/TextField' 16 + import { 17 + displayDuration, 18 + getLiveServiceNames, 19 + useDebouncedValue, 20 + } from '#/components/live/utils' 21 import {Loader} from '#/components/Loader' 22 import * as ProfileCard from '#/components/ProfileCard' 23 import * as Select from '#/components/Select' ··· 25 import type * as bsky from '#/types/bsky' 26 import {LinkPreview} from './LinkPreview' 27 import {useLiveLinkMetaQuery, useUpsertLiveStatusMutation} from './queries' 28 29 export function GoLiveDialog({ 30 control, ··· 60 61 const time = useCallback( 62 (offset: number) => { 63 + void tick 64 65 const date = new Date() 66 date.setMinutes(date.getMinutes() + offset)
+2 -2
src/components/moderation/LabelsOnMeDialog.tsx
··· 12 import {makeProfileLink} from '#/lib/routes/links' 13 import {sanitizeHandle} from '#/lib/strings/handles' 14 import {logger} from '#/logger' 15 - import {isAndroid} from '#/platform/detection' 16 import {useAgent, useSession} from '#/state/session' 17 import * as Toast from '#/view/com/util/Toast' 18 import {atoms as a, useBreakpoints, useTheme} from '#/alf' ··· 20 import * as Dialog from '#/components/Dialog' 21 import {InlineLinkText} from '#/components/Link' 22 import {Text} from '#/components/Typography' 23 import {Admonition} from '../Admonition' 24 import {Divider} from '../Divider' 25 import {Loader} from '../Loader' ··· 344 {isPending && <ButtonIcon icon={Loader} />} 345 </Button> 346 </View> 347 - {isAndroid && <View style={{height: 300}} />} 348 </> 349 ) 350 }
··· 12 import {makeProfileLink} from '#/lib/routes/links' 13 import {sanitizeHandle} from '#/lib/strings/handles' 14 import {logger} from '#/logger' 15 import {useAgent, useSession} from '#/state/session' 16 import * as Toast from '#/view/com/util/Toast' 17 import {atoms as a, useBreakpoints, useTheme} from '#/alf' ··· 19 import * as Dialog from '#/components/Dialog' 20 import {InlineLinkText} from '#/components/Link' 21 import {Text} from '#/components/Typography' 22 + import {IS_ANDROID} from '#/env' 23 import {Admonition} from '../Admonition' 24 import {Divider} from '../Divider' 25 import {Loader} from '../Loader' ··· 344 {isPending && <ButtonIcon icon={Loader} />} 345 </Button> 346 </View> 347 + {IS_ANDROID && <View style={{height: 300}} />} 348 </> 349 ) 350 }
+3 -3
src/components/moderation/ModerationDetailsDialog.tsx
··· 7 import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 8 import {makeProfileLink} from '#/lib/routes/links' 9 import {listUriToHref} from '#/lib/strings/url-helpers' 10 - import {isNative} from '#/platform/detection' 11 import {useSession} from '#/state/session' 12 import {atoms as a, useGutters, useTheme} from '#/alf' 13 import * as Dialog from '#/components/Dialog' 14 import {InlineLinkText} from '#/components/Link' 15 import {type AppModerationCause} from '#/components/Pills' 16 import {Text} from '#/components/Typography' 17 18 export {useDialogControl as useModerationDetailsDialogControl} from '#/components/Dialog' 19 ··· 158 xGutters, 159 a.py_md, 160 a.border_t, 161 - !isNative && t.atoms.bg_contrast_25, 162 t.atoms.border_contrast_low, 163 { 164 borderBottomLeftRadius: a.rounded_md.borderRadius, ··· 219 </View> 220 )} 221 222 - {isNative && <View style={{height: 40}} />} 223 224 <Dialog.Close /> 225 </Dialog.ScrollableInner>
··· 7 import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 8 import {makeProfileLink} from '#/lib/routes/links' 9 import {listUriToHref} from '#/lib/strings/url-helpers' 10 import {useSession} from '#/state/session' 11 import {atoms as a, useGutters, useTheme} from '#/alf' 12 import * as Dialog from '#/components/Dialog' 13 import {InlineLinkText} from '#/components/Link' 14 import {type AppModerationCause} from '#/components/Pills' 15 import {Text} from '#/components/Typography' 16 + import {IS_NATIVE} from '#/env' 17 18 export {useDialogControl as useModerationDetailsDialogControl} from '#/components/Dialog' 19 ··· 158 xGutters, 159 a.py_md, 160 a.border_t, 161 + !IS_NATIVE && t.atoms.bg_contrast_25, 162 t.atoms.border_contrast_low, 163 { 164 borderBottomLeftRadius: a.rounded_md.borderRadius, ··· 219 </View> 220 )} 221 222 + {IS_NATIVE && <View style={{height: 40}} />} 223 224 <Dialog.Close /> 225 </Dialog.ScrollableInner>
+4 -4
src/components/moderation/ReportDialog/index.tsx
··· 8 import {getLabelingServiceTitle} from '#/lib/moderation' 9 import {sanitizeHandle} from '#/lib/strings/handles' 10 import {Logger} from '#/logger' 11 - import {isNative} from '#/platform/detection' 12 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 13 import {useMyLabelersQuery} from '#/state/queries/preferences' 14 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' ··· 30 import {createStaticClick, InlineLinkText, Link} from '#/components/Link' 31 import {Loader} from '#/components/Loader' 32 import {Text} from '#/components/Typography' 33 import {useSubmitReportMutation} from './action' 34 import { 35 BSKY_LABELER_ONLY_REPORT_REASONS, ··· 214 logger.metric( 215 'reportDialog:success', 216 { 217 - reason: state.selectedOption?.reason!, 218 - labeler: state.selectedLabeler?.creator.handle!, 219 details: !!state.details, 220 }, 221 {statsig: false}, ··· 256 label={_(msg`Report dialog`)} 257 ref={ref} 258 style={[a.w_full, {maxWidth: 500}]}> 259 - <View style={[a.gap_2xl, isNative && a.pt_md]}> 260 <StepOuter> 261 <StepTitle 262 index={1}
··· 8 import {getLabelingServiceTitle} from '#/lib/moderation' 9 import {sanitizeHandle} from '#/lib/strings/handles' 10 import {Logger} from '#/logger' 11 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 12 import {useMyLabelersQuery} from '#/state/queries/preferences' 13 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' ··· 29 import {createStaticClick, InlineLinkText, Link} from '#/components/Link' 30 import {Loader} from '#/components/Loader' 31 import {Text} from '#/components/Typography' 32 + import {IS_NATIVE} from '#/env' 33 import {useSubmitReportMutation} from './action' 34 import { 35 BSKY_LABELER_ONLY_REPORT_REASONS, ··· 214 logger.metric( 215 'reportDialog:success', 216 { 217 + reason: state.selectedOption?.reason ?? '', 218 + labeler: state.selectedLabeler?.creator.handle ?? '', 219 details: !!state.details, 220 }, 221 {statsig: false}, ··· 256 label={_(msg`Report dialog`)} 257 ref={ref} 258 style={[a.w_full, {maxWidth: 500}]}> 259 + <View style={[a.gap_2xl, IS_NATIVE && a.pt_md]}> 260 <StepOuter> 261 <StepTitle 262 index={1}
+24
src/env/index.ts
··· 1 import {nativeBuildVersion} from 'expo-application' 2 3 import {BUNDLE_IDENTIFIER, IS_TESTFLIGHT, RELEASE_VERSION} from '#/env/common' ··· 17 export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${ 18 __DEV__ ? 'dev' : IS_TESTFLIGHT ? 'tf' : 'prod' 19 })`
··· 1 + import {Platform} from 'react-native' 2 import {nativeBuildVersion} from 'expo-application' 3 4 import {BUNDLE_IDENTIFIER, IS_TESTFLIGHT, RELEASE_VERSION} from '#/env/common' ··· 18 export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${ 19 __DEV__ ? 'dev' : IS_TESTFLIGHT ? 'tf' : 'prod' 20 })` 21 + 22 + /** 23 + * Platform detection 24 + */ 25 + export const IS_IOS: boolean = Platform.OS === 'ios' 26 + export const IS_ANDROID: boolean = Platform.OS === 'android' 27 + export const IS_NATIVE: boolean = true 28 + export const IS_WEB: boolean = false 29 + 30 + /** 31 + * Web-specific platform detection 32 + */ 33 + export const IS_WEB_TOUCH_DEVICE: boolean = true 34 + export const IS_WEB_MOBILE: boolean = false 35 + export const IS_WEB_MOBILE_IOS: boolean = false 36 + export const IS_WEB_MOBILE_ANDROID: boolean = false 37 + export const IS_WEB_SAFARI: boolean = false 38 + export const IS_WEB_FIREFOX: boolean = false 39 + 40 + /** 41 + * Misc 42 + */ 43 + export const IS_HIGH_DPI: boolean = true
+34
src/env/index.web.ts
··· 13 * The short commit hash and environment of the current bundle. 14 */ 15 export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${__DEV__ ? 'dev' : 'prod'})`
··· 13 * The short commit hash and environment of the current bundle. 14 */ 15 export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${__DEV__ ? 'dev' : 'prod'})` 16 + 17 + /** 18 + * Platform detection 19 + */ 20 + export const IS_IOS: boolean = false 21 + export const IS_ANDROID: boolean = false 22 + export const IS_NATIVE: boolean = false 23 + export const IS_WEB: boolean = true 24 + 25 + /** 26 + * Web-specific platform detection 27 + */ 28 + export const IS_WEB_TOUCH_DEVICE = 29 + window.matchMedia('(pointer: coarse)').matches 30 + export const IS_WEB_MOBILE: boolean = window.matchMedia( 31 + 'only screen and (max-width: 1300px)', 32 + )?.matches 33 + export const IS_WEB_MOBILE_IOS: boolean = /iPhone/.test(navigator.userAgent) 34 + export const IS_WEB_MOBILE_ANDROID: boolean = 35 + /android/i.test(navigator.userAgent) && IS_WEB_TOUCH_DEVICE 36 + export const IS_WEB_SAFARI: boolean = /^((?!chrome|android).)*safari/i.test( 37 + // https://stackoverflow.com/questions/7944460/detect-safari-browser 38 + navigator.userAgent, 39 + ) 40 + export const IS_WEB_FIREFOX: boolean = /firefox|fxios/i.test( 41 + navigator.userAgent, 42 + ) 43 + 44 + /** 45 + * Misc 46 + */ 47 + export const IS_HIGH_DPI: boolean = window.matchMedia( 48 + '(min-resolution: 2dppx)', 49 + ).matches
+12 -14
src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx
··· 3 import {useLingui} from '@lingui/react' 4 5 import {useCleanError} from '#/lib/hooks/useCleanError' 6 - import {isNative} from '#/platform/detection' 7 import {atoms as a, web} from '#/alf' 8 import {Admonition} from '#/components/Admonition' 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 import * as Dialog from '#/components/Dialog' 11 import {Loader} from '#/components/Loader' 12 - import * as toast from '#/components/Toast' 13 import {Span, Text} from '#/components/Typography' 14 import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences' 15 import { 16 type LiveEventFeed, ··· 61 feed, 62 metricContext, 63 onUpdateSuccess({undoAction}) { 64 - toast.show( 65 - <toast.Outer> 66 - <toast.Icon /> 67 - <toast.Text> 68 <Trans>Your live event preferences have been updated.</Trans> 69 - </toast.Text> 70 {undoAction && ( 71 - <toast.Action 72 label={_(msg`Undo`)} 73 onPress={() => { 74 if (undoAction) { ··· 76 } 77 }}> 78 <Trans>Undo</Trans> 79 - </toast.Action> 80 )} 81 - </toast.Outer>, 82 - { 83 - type: 'success', 84 - }, 85 ) 86 87 /* ··· 148 </ButtonText> 149 {isHidingAllFeeds && <ButtonIcon icon={Loader} />} 150 </Button> 151 - {isNative && ( 152 <Button 153 label={_(msg`Cancel`)} 154 size="large"
··· 3 import {useLingui} from '@lingui/react' 4 5 import {useCleanError} from '#/lib/hooks/useCleanError' 6 import {atoms as a, web} from '#/alf' 7 import {Admonition} from '#/components/Admonition' 8 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 9 import * as Dialog from '#/components/Dialog' 10 import {Loader} from '#/components/Loader' 11 + import * as Toast from '#/components/Toast' 12 import {Span, Text} from '#/components/Typography' 13 + import {IS_NATIVE} from '#/env' 14 import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences' 15 import { 16 type LiveEventFeed, ··· 61 feed, 62 metricContext, 63 onUpdateSuccess({undoAction}) { 64 + Toast.show( 65 + <Toast.Outer> 66 + <Toast.Icon /> 67 + <Toast.Text> 68 <Trans>Your live event preferences have been updated.</Trans> 69 + </Toast.Text> 70 {undoAction && ( 71 + <Toast.Action 72 label={_(msg`Undo`)} 73 onPress={() => { 74 if (undoAction) { ··· 76 } 77 }}> 78 <Trans>Undo</Trans> 79 + </Toast.Action> 80 )} 81 + </Toast.Outer>, 82 + {type: 'success'}, 83 ) 84 85 /* ··· 146 </ButtonText> 147 {isHidingAllFeeds && <ButtonIcon icon={Loader} />} 148 </Button> 149 + {IS_NATIVE && ( 150 <Button 151 label={_(msg`Cancel`)} 152 size="large"
+2 -2
src/features/liveEvents/preferences.ts
··· 3 import {useMutation, useQueryClient} from '@tanstack/react-query' 4 5 import {logger} from '#/logger' 6 - import {isWeb} from '#/platform/detection' 7 import { 8 preferencesQueryKey, 9 usePreferencesQuery, 10 } from '#/state/queries/preferences' 11 import {useAgent} from '#/state/session' 12 import * as env from '#/env' 13 import { 14 type LiveEventFeed, ··· 41 const agent = useAgent() 42 43 useEffect(() => { 44 - if (env.IS_DEV && isWeb && typeof window !== 'undefined') { 45 // @ts-ignore 46 window.__updateLiveEventPreferences = async ( 47 action: LiveEventPreferencesAction,
··· 3 import {useMutation, useQueryClient} from '@tanstack/react-query' 4 5 import {logger} from '#/logger' 6 import { 7 preferencesQueryKey, 8 usePreferencesQuery, 9 } from '#/state/queries/preferences' 10 import {useAgent} from '#/state/session' 11 + import {IS_WEB} from '#/env' 12 import * as env from '#/env' 13 import { 14 type LiveEventFeed, ··· 41 const agent = useAgent() 42 43 useEffect(() => { 44 + if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') { 45 // @ts-ignore 46 window.__updateLiveEventPreferences = async ( 47 action: LiveEventPreferencesAction,
+2 -2
src/geolocation/device.ts
··· 3 import * as Location from 'expo-location' 4 import {createPermissionHook} from 'expo-modules-core' 5 6 - import {isNative} from '#/platform/detection' 7 import * as debug from '#/geolocation/debug' 8 import {logger} from '#/geolocation/logger' 9 import {type Geolocation} from '#/geolocation/types' ··· 118 const synced = useRef(false) 119 const [status] = useForegroundPermissions() 120 useEffect(() => { 121 - if (!isNative) return 122 123 async function get() { 124 // no need to set this more than once per session
··· 3 import * as Location from 'expo-location' 4 import {createPermissionHook} from 'expo-modules-core' 5 6 + import {IS_NATIVE} from '#/env' 7 import * as debug from '#/geolocation/debug' 8 import {logger} from '#/geolocation/logger' 9 import {type Geolocation} from '#/geolocation/types' ··· 118 const synced = useRef(false) 119 const [status] = useForegroundPermissions() 120 useEffect(() => { 121 + if (!IS_NATIVE) return 122 123 async function get() { 124 // no need to set this more than once per session
+2 -2
src/geolocation/service.ts
··· 127 }) 128 129 useEffect(() => { 130 - return onGeolocationServiceResponseUpdate(responseConfig => { 131 - setConfig(responseConfig!) 132 }) 133 }, []) 134
··· 127 }) 128 129 useEffect(() => { 130 + return onGeolocationServiceResponseUpdate(config => { 131 + setConfig(config) 132 }) 133 }, []) 134
+2 -2
src/geolocation/util.ts
··· 1 import {type LocationGeocodedAddress} from 'expo-location' 2 3 - import {isAndroid} from '#/platform/detection' 4 import {logger} from '#/geolocation/logger' 5 import {type Geolocation} from '#/geolocation/types' 6 ··· 81 /* 82 * Android doesn't give us ISO 3166-2 short codes. We need these for US 83 */ 84 - if (isAndroid) { 85 if (region && isoCountryCode === 'US') { 86 /* 87 * We need short codes for US states. If we can't remap it, just drop it
··· 1 import {type LocationGeocodedAddress} from 'expo-location' 2 3 + import {IS_ANDROID} from '#/env' 4 import {logger} from '#/geolocation/logger' 5 import {type Geolocation} from '#/geolocation/types' 6 ··· 81 /* 82 * Android doesn't give us ISO 3166-2 short codes. We need these for US 83 */ 84 + if (IS_ANDROID) { 85 if (region && isoCountryCode === 'US') { 86 /* 87 * We need short codes for US states. If we can't remap it, just drop it
+1 -1
src/lib/actor-status.ts
··· 17 const config = useLiveNowConfig() 18 19 return useMemo(() => { 20 - tick! // revalidate every minute 21 22 if (shadowed && 'status' in shadowed && shadowed.status) { 23 const isValid = validateStatus(shadowed.status, config)
··· 17 const config = useLiveNowConfig() 18 19 return useMemo(() => { 20 + void tick // revalidate every minute 21 22 if (shadowed && 'status' in shadowed && shadowed.status) { 23 const isValid = validateStatus(shadowed.status, config)
+2 -2
src/lib/api/feed/utils.ts
··· 1 import {AtUri} from '@atproto/api' 2 3 import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' 4 - import {isWeb} from '#/platform/detection' 5 import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' 6 7 let debugTopics = '' 8 - if (isWeb && typeof window !== 'undefined') { 9 const params = new URLSearchParams(window.location.search) 10 debugTopics = params.get('debug_topics') ?? '' 11 }
··· 1 import {AtUri} from '@atproto/api' 2 3 import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' 4 import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' 5 + import {IS_WEB} from '#/env' 6 7 let debugTopics = '' 8 + if (IS_WEB && typeof window !== 'undefined') { 9 const params = new URLSearchParams(window.location.search) 10 debugTopics = params.get('debug_topics') ?? '' 11 }
-5
src/lib/browser.native.ts
··· 1 - export const isSafari = false 2 - export const isFirefox = false 3 - export const isTouchDevice = true 4 - export const isAndroidWeb = false 5 - export const isHighDPI = true
···
-9
src/lib/browser.ts
··· 1 - // https://stackoverflow.com/questions/7944460/detect-safari-browser 2 - export const isSafari = /^((?!chrome|android).)*safari/i.test( 3 - navigator.userAgent, 4 - ) 5 - export const isFirefox = /firefox|fxios/i.test(navigator.userAgent) 6 - export const isTouchDevice = window.matchMedia('(pointer: coarse)').matches 7 - export const isAndroidWeb = 8 - /android/i.test(navigator.userAgent) && isTouchDevice 9 - export const isHighDPI = window.matchMedia('(min-resolution: 2dppx)').matches
···
+3 -3
src/lib/custom-animations/AccordionAnimation.tsx
··· 13 withTiming, 14 } from 'react-native-reanimated' 15 16 - import {isIOS, isWeb} from '#/platform/detection' 17 18 type AccordionAnimationProps = React.PropsWithChildren<{ 19 isExpanded: boolean ··· 66 style={style} 67 entering={FadeInUp.duration(duration)} 68 exiting={FadeOutUp.duration(duration / 2)} 69 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 70 {children} 71 </Animated.View> 72 ) 73 } 74 75 export function AccordionAnimation(props: AccordionAnimationProps) { 76 - return isWeb ? <WebAccordion {...props} /> : <MobileAccordion {...props} /> 77 }
··· 13 withTiming, 14 } from 'react-native-reanimated' 15 16 + import {IS_IOS, IS_WEB} from '#/env' 17 18 type AccordionAnimationProps = React.PropsWithChildren<{ 19 isExpanded: boolean ··· 66 style={style} 67 entering={FadeInUp.duration(duration)} 68 exiting={FadeOutUp.duration(duration / 2)} 69 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 70 {children} 71 </Animated.View> 72 ) 73 } 74 75 export function AccordionAnimation(props: AccordionAnimationProps) { 76 + return IS_WEB ? <WebAccordion {...props} /> : <MobileAccordion {...props} /> 77 }
+2 -3
src/lib/custom-animations/PressableScale.tsx
··· 12 withTiming, 13 } from 'react-native-reanimated' 14 15 - import {isTouchDevice} from '#/lib/browser' 16 - import {isNative} from '#/platform/detection' 17 18 - const DEFAULT_TARGET_SCALE = isNative || isTouchDevice ? 0.98 : 1 19 20 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 21
··· 12 withTiming, 13 } from 'react-native-reanimated' 14 15 + import {IS_NATIVE, IS_WEB_TOUCH_DEVICE} from '#/env' 16 17 + const DEFAULT_TARGET_SCALE = IS_NATIVE || IS_WEB_TOUCH_DEVICE ? 0.98 : 1 18 19 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 20
+1 -1
src/lib/functions.ts
··· 67 } 68 69 // Copied from: https://github.com/jonschlinkert/is-plain-object 70 - export function isPlainObject(o: any): o is Object { 71 if (!hasObjectPrototype(o)) { 72 return false 73 }
··· 67 } 68 69 // Copied from: https://github.com/jonschlinkert/is-plain-object 70 + export function isPlainObject(o: any): o is object { 71 if (!hasObjectPrototype(o)) { 72 return false 73 }
+3 -3
src/lib/haptics.ts
··· 2 import * as Device from 'expo-device' 3 import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics' 4 5 - import {isIOS, isWeb} from '#/platform/detection' 6 import {useHapticsDisabled} from '#/state/preferences/disable-haptics' 7 8 export function useHaptics() { 9 const isHapticsDisabled = useHapticsDisabled() 10 11 return React.useCallback( 12 (strength: 'Light' | 'Medium' | 'Heavy' = 'Medium') => { 13 - if (isHapticsDisabled || isWeb) { 14 return 15 } 16 17 // Users said the medium impact was too strong on Android; see APP-537s 18 - const style = isIOS 19 ? ImpactFeedbackStyle[strength] 20 : ImpactFeedbackStyle.Light 21 impactAsync(style)
··· 2 import * as Device from 'expo-device' 3 import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics' 4 5 import {useHapticsDisabled} from '#/state/preferences/disable-haptics' 6 + import {IS_IOS, IS_WEB} from '#/env' 7 8 export function useHaptics() { 9 const isHapticsDisabled = useHapticsDisabled() 10 11 return React.useCallback( 12 (strength: 'Light' | 'Medium' | 'Heavy' = 'Medium') => { 13 + if (isHapticsDisabled || IS_WEB) { 14 return 15 } 16 17 // Users said the medium impact was too strong on Android; see APP-537s 18 + const style = IS_IOS 19 ? ImpactFeedbackStyle[strength] 20 : ImpactFeedbackStyle.Light 21 impactAsync(style)
+2 -2
src/lib/hooks/useBottomBarOffset.ts
··· 2 3 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 4 import {clamp} from '#/lib/numbers' 5 - import {isWeb} from '#/platform/detection' 6 7 export function useBottomBarOffset(modifier: number = 0) { 8 const {isTabletOrDesktop} = useWebMediaQueries() 9 const {bottom: bottomInset} = useSafeAreaInsets() 10 return ( 11 - (isWeb && isTabletOrDesktop ? 0 : clamp(60 + bottomInset, 60, 75)) + 12 modifier 13 ) 14 }
··· 2 3 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 4 import {clamp} from '#/lib/numbers' 5 + import {IS_WEB} from '#/env' 6 7 export function useBottomBarOffset(modifier: number = 0) { 8 const {isTabletOrDesktop} = useWebMediaQueries() 9 const {bottom: bottomInset} = useSafeAreaInsets() 10 return ( 11 + (IS_WEB && isTabletOrDesktop ? 0 : clamp(60 + bottomInset, 60, 75)) + 12 modifier 13 ) 14 }
+3 -3
src/lib/hooks/useIntentHandler.ts
··· 6 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 7 import {parseLinkingUrl} from '#/lib/parseLinkingUrl' 8 import {logger} from '#/logger' 9 - import {isIOS, isNative} from '#/platform/detection' 10 import {useSession} from '#/state/session' 11 import {useCloseAllActiveElements} from '#/state/util' 12 import {useIntentDialogs} from '#/components/intents/IntentDialogs' 13 import {Referrer} from '../../../modules/expo-bluesky-swiss-army' 14 import {useApplyPullRequestOTAUpdate} from './useOTAUpdates' 15 ··· 29 30 React.useEffect(() => { 31 const handleIncomingURL = async (url: string) => { 32 - if (isIOS) { 33 // Close in-app browser if it's open (iOS only) 34 await WebBrowser.dismissBrowser().catch(() => {}) 35 } ··· 150 setTimeout(() => { 151 openComposer({ 152 text: text ?? undefined, 153 - imageUris: isNative ? imageUris : undefined, 154 }) 155 }, 500) 156 },
··· 6 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 7 import {parseLinkingUrl} from '#/lib/parseLinkingUrl' 8 import {logger} from '#/logger' 9 import {useSession} from '#/state/session' 10 import {useCloseAllActiveElements} from '#/state/util' 11 import {useIntentDialogs} from '#/components/intents/IntentDialogs' 12 + import {IS_IOS, IS_NATIVE} from '#/env' 13 import {Referrer} from '../../../modules/expo-bluesky-swiss-army' 14 import {useApplyPullRequestOTAUpdate} from './useOTAUpdates' 15 ··· 29 30 React.useEffect(() => { 31 const handleIncomingURL = async (url: string) => { 32 + if (IS_IOS) { 33 // Close in-app browser if it's open (iOS only) 34 await WebBrowser.dismissBrowser().catch(() => {}) 35 } ··· 150 setTimeout(() => { 151 openComposer({ 152 text: text ?? undefined, 153 + imageUris: IS_NATIVE ? imageUris : undefined, 154 }) 155 }, 500) 156 },
+3 -3
src/lib/hooks/useIsKeyboardVisible.ts
··· 1 import {useEffect, useState} from 'react' 2 import {Keyboard} from 'react-native' 3 4 - import {isIOS} from '#/platform/detection' 5 6 export function useIsKeyboardVisible({ 7 iosUseWillEvents, ··· 14 // only iOS supports the "will" events 15 // -prf 16 const showEvent = 17 - isIOS && iosUseWillEvents ? 'keyboardWillShow' : 'keyboardDidShow' 18 const hideEvent = 19 - isIOS && iosUseWillEvents ? 'keyboardWillHide' : 'keyboardDidHide' 20 21 useEffect(() => { 22 const keyboardShowListener = Keyboard.addListener(showEvent, () =>
··· 1 import {useEffect, useState} from 'react' 2 import {Keyboard} from 'react-native' 3 4 + import {IS_IOS} from '#/env' 5 6 export function useIsKeyboardVisible({ 7 iosUseWillEvents, ··· 14 // only iOS supports the "will" events 15 // -prf 16 const showEvent = 17 + IS_IOS && iosUseWillEvents ? 'keyboardWillShow' : 'keyboardDidShow' 18 const hideEvent = 19 + IS_IOS && iosUseWillEvents ? 'keyboardWillHide' : 'keyboardDidHide' 20 21 useEffect(() => { 22 const keyboardShowListener = Keyboard.addListener(showEvent, () =>
+3 -3
src/lib/hooks/useNotificationHandler.ts
··· 9 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 10 import {logger as notyLogger} from '#/lib/notifications/util' 11 import {type NavigationProp} from '#/lib/routes/types' 12 - import {isAndroid, isIOS} from '#/platform/detection' 13 import {useCurrentConvoId} from '#/state/messages/current-convo-id' 14 import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed' 15 import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread' ··· 17 import {useSession} from '#/state/session' 18 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 19 import {useCloseAllActiveElements} from '#/state/util' 20 import {resetToTab} from '#/Navigation' 21 import {router} from '#/routes' 22 ··· 90 // channels allow for the mute/unmute functionality we want for the background 91 // handler. 92 useEffect(() => { 93 - if (!isAndroid) return 94 // assign both chat notifications to a group 95 // NOTE: I don't think that it will retroactively move them into the group 96 // if the channels already exist. no big deal imo -sfn ··· 379 } 380 381 const payload = ( 382 - isIOS ? e.request.trigger.payload : e.request.content.data 383 ) as NotificationPayload 384 385 if (payload && payload.reason) {
··· 9 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 10 import {logger as notyLogger} from '#/lib/notifications/util' 11 import {type NavigationProp} from '#/lib/routes/types' 12 import {useCurrentConvoId} from '#/state/messages/current-convo-id' 13 import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed' 14 import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread' ··· 16 import {useSession} from '#/state/session' 17 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 18 import {useCloseAllActiveElements} from '#/state/util' 19 + import {IS_ANDROID, IS_IOS} from '#/env' 20 import {resetToTab} from '#/Navigation' 21 import {router} from '#/routes' 22 ··· 90 // channels allow for the mute/unmute functionality we want for the background 91 // handler. 92 useEffect(() => { 93 + if (!IS_ANDROID) return 94 // assign both chat notifications to a group 95 // NOTE: I don't think that it will retroactively move them into the group 96 // if the channels already exist. no big deal imo -sfn ··· 379 } 380 381 const payload = ( 382 + IS_IOS ? e.request.trigger.payload : e.request.content.data 383 ) as NotificationPayload 384 385 if (payload && payload.reason) {
+4 -4
src/lib/hooks/useOTAUpdates.ts
··· 12 13 import {isNetworkError} from '#/lib/strings/errors' 14 import {logger} from '#/logger' 15 - import {isAndroid, isIOS} from '#/platform/detection' 16 import {IS_TESTFLIGHT} from '#/env' 17 18 const MINIMUM_MINIMIZE_TIME = 15 * 60e3 19 20 async function setExtraParams() { 21 await setExtraParamAsync( 22 - isIOS ? 'ios-build-number' : 'android-build-number', 23 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 24 // This just ensures it gets passed as a string 25 `${nativeBuildVersion}`, ··· 32 33 async function setExtraParamsPullRequest(channel: string) { 34 await setExtraParamAsync( 35 - isIOS ? 'ios-build-number' : 'android-build-number', 36 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 37 // This just ensures it gets passed as a string 38 `${nativeBuildVersion}`, ··· 198 // `maintainVisibleContentPosition`. See repro repo for more details: 199 // https://github.com/mozzius/ota-crash-repro 200 // Old Arch only - re-enable once we're on the New Archictecture! -sfn 201 - if (isAndroid) return 202 203 const subscription = AppState.addEventListener( 204 'change',
··· 12 13 import {isNetworkError} from '#/lib/strings/errors' 14 import {logger} from '#/logger' 15 + import {IS_ANDROID, IS_IOS} from '#/env' 16 import {IS_TESTFLIGHT} from '#/env' 17 18 const MINIMUM_MINIMIZE_TIME = 15 * 60e3 19 20 async function setExtraParams() { 21 await setExtraParamAsync( 22 + IS_IOS ? 'ios-build-number' : 'android-build-number', 23 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 24 // This just ensures it gets passed as a string 25 `${nativeBuildVersion}`, ··· 32 33 async function setExtraParamsPullRequest(channel: string) { 34 await setExtraParamAsync( 35 + IS_IOS ? 'ios-build-number' : 'android-build-number', 36 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 37 // This just ensures it gets passed as a string 38 `${nativeBuildVersion}`, ··· 198 // `maintainVisibleContentPosition`. See repro repo for more details: 199 // https://github.com/mozzius/ota-crash-repro 200 // Old Arch only - re-enable once we're on the New Archictecture! -sfn 201 + if (IS_ANDROID) return 202 203 const subscription = AppState.addEventListener( 204 'change',
+2 -2
src/lib/hooks/useOpenLink.ts
··· 12 toNiceDomain, 13 } from '#/lib/strings/url-helpers' 14 import {logger} from '#/logger' 15 - import {isNative} from '#/platform/detection' 16 import {useInAppBrowser} from '#/state/preferences/in-app-browser' 17 import {useTheme} from '#/alf' 18 import {useDialogContext} from '#/components/Dialog' 19 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 20 21 export function useOpenLink() { 22 const enabled = useInAppBrowser() ··· 41 } 42 } 43 44 - if (isNative && !url.startsWith('mailto:')) { 45 if (override === undefined && enabled === undefined) { 46 // consent dialog is a global dialog, and while it's possible to nest dialogs, 47 // the actual components need to be nested. sibling dialogs on iOS are not supported.
··· 12 toNiceDomain, 13 } from '#/lib/strings/url-helpers' 14 import {logger} from '#/logger' 15 import {useInAppBrowser} from '#/state/preferences/in-app-browser' 16 import {useTheme} from '#/alf' 17 import {useDialogContext} from '#/components/Dialog' 18 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 19 + import {IS_NATIVE} from '#/env' 20 21 export function useOpenLink() { 22 const enabled = useInAppBrowser() ··· 41 } 42 } 43 44 + if (IS_NATIVE && !url.startsWith('mailto:')) { 45 if (override === undefined && enabled === undefined) { 46 // consent dialog is a global dialog, and while it's possible to nest dialogs, 47 // the actual components need to be nested. sibling dialogs on iOS are not supported.
+3 -3
src/lib/hooks/usePermissions.ts
··· 2 import {useCameraPermissions as useExpoCameraPermissions} from 'expo-camera' 3 import * as MediaLibrary from 'expo-media-library' 4 5 - import {isWeb} from '#/platform/detection' 6 import {Alert} from '#/view/com/util/Alert' 7 8 const openPermissionAlert = (perm: string) => { 9 Alert.alert( ··· 26 const requestPhotoAccessIfNeeded = async () => { 27 // On the, we use <input type="file"> to produce a filepicker 28 // This does not need any permission granting. 29 - if (isWeb) { 30 return true 31 } 32 ··· 55 const requestVideoAccessIfNeeded = async () => { 56 // On the, we use <input type="file"> to produce a filepicker 57 // This does not need any permission granting. 58 - if (isWeb) { 59 return true 60 } 61
··· 2 import {useCameraPermissions as useExpoCameraPermissions} from 'expo-camera' 3 import * as MediaLibrary from 'expo-media-library' 4 5 import {Alert} from '#/view/com/util/Alert' 6 + import {IS_WEB} from '#/env' 7 8 const openPermissionAlert = (perm: string) => { 9 Alert.alert( ··· 26 const requestPhotoAccessIfNeeded = async () => { 27 // On the, we use <input type="file"> to produce a filepicker 28 // This does not need any permission granting. 29 + if (IS_WEB) { 30 return true 31 } 32 ··· 55 const requestVideoAccessIfNeeded = async () => { 56 // On the, we use <input type="file"> to produce a filepicker 57 // This does not need any permission granting. 58 + if (IS_WEB) { 59 return true 60 } 61
+2 -2
src/lib/hooks/useTranslate.ts
··· 2 import * as IntentLauncher from 'expo-intent-launcher' 3 4 import {getTranslatorLink} from '#/locale/helpers' 5 - import {isAndroid} from '#/platform/detection' 6 import {useOpenLink} from './useOpenLink' 7 8 export function useTranslate() { ··· 11 return useCallback( 12 async (text: string, language: string) => { 13 const translateUrl = getTranslatorLink(text, language) 14 - if (isAndroid) { 15 try { 16 // use getApplicationIconAsync to determine if the translate app is installed 17 if (
··· 2 import * as IntentLauncher from 'expo-intent-launcher' 3 4 import {getTranslatorLink} from '#/locale/helpers' 5 + import {IS_ANDROID} from '#/env' 6 import {useOpenLink} from './useOpenLink' 7 8 export function useTranslate() { ··· 11 return useCallback( 12 async (text: string, language: string) => { 13 const translateUrl = getTranslatorLink(text, language) 14 + if (IS_ANDROID) { 15 try { 16 // use getApplicationIconAsync to determine if the translate app is installed 17 if (
+2 -2
src/lib/hooks/useWebMediaQueries.tsx
··· 1 import {useMediaQuery} from 'react-responsive' 2 3 - import {isNative} from '#/platform/detection' 4 5 /** 6 * @deprecated use `useBreakpoints` from `#/alf` instead ··· 11 const isMobile = useMediaQuery({maxWidth: 800 - 1}) 12 const isTabletOrMobile = isMobile || isTablet 13 const isTabletOrDesktop = isDesktop || isTablet 14 - if (isNative) { 15 return { 16 isMobile: true, 17 isTablet: false,
··· 1 import {useMediaQuery} from 'react-responsive' 2 3 + import {IS_NATIVE} from '#/env' 4 5 /** 6 * @deprecated use `useBreakpoints` from `#/alf` instead ··· 11 const isMobile = useMediaQuery({maxWidth: 800 - 1}) 12 const isTabletOrMobile = isMobile || isTablet 13 const isTabletOrDesktop = isDesktop || isTablet 14 + if (IS_NATIVE) { 15 return { 16 isMobile: true, 17 isTablet: false,
+4 -4
src/lib/media/manip.ts
··· 18 19 import {POST_IMG_MAX} from '#/lib/constants' 20 import {logger} from '#/logger' 21 - import {isAndroid, isIOS} from '#/platform/detection' 22 import {type PickerImage} from './picker.shared' 23 import {type Dimensions} from './types' 24 import {mimeToExt} from './video/util' ··· 109 110 // save 111 try { 112 - if (isAndroid) { 113 // android triggers an annoying permission prompt if you try and move an image 114 // between albums. therefore, we need to either create the album with the image 115 // as the starting image, or put it directly into the album ··· 327 } 328 329 function normalizePath(str: string, allPlatforms = false): string { 330 - if (isAndroid || allPlatforms) { 331 if (!str.startsWith('file://')) { 332 return `file://${str}` 333 } ··· 350 type: string, 351 ) { 352 try { 353 - if (isIOS) { 354 await withTempFile(filename, encoded, async tmpFileUrl => { 355 await Sharing.shareAsync(tmpFileUrl, {UTI: type}) 356 })
··· 18 19 import {POST_IMG_MAX} from '#/lib/constants' 20 import {logger} from '#/logger' 21 + import {IS_ANDROID, IS_IOS} from '#/env' 22 import {type PickerImage} from './picker.shared' 23 import {type Dimensions} from './types' 24 import {mimeToExt} from './video/util' ··· 109 110 // save 111 try { 112 + if (IS_ANDROID) { 113 // android triggers an annoying permission prompt if you try and move an image 114 // between albums. therefore, we need to either create the album with the image 115 // as the starting image, or put it directly into the album ··· 327 } 328 329 function normalizePath(str: string, allPlatforms = false): string { 330 + if (IS_ANDROID || allPlatforms) { 331 if (!str.startsWith('file://')) { 332 return `file://${str}` 333 } ··· 350 type: string, 351 ) { 352 try { 353 + if (IS_IOS) { 354 await withTempFile(filename, encoded, async tmpFileUrl => { 355 await Sharing.shareAsync(tmpFileUrl, {UTI: type}) 356 })
+1 -1
src/lib/media/picker.e2e.tsx
··· 3 getInfoAsync, 4 readDirectoryAsync, 5 } from 'expo-file-system/legacy' 6 import ExpoImageCropTool, { 7 type OpenCropperOptions, 8 } from '@bsky.app/expo-image-crop-tool' 9 10 import {compressIfNeeded} from './manip' 11 import {type PickerImage} from './picker.shared' 12 - import {ImagePickerResult} from 'expo-image-picker' 13 14 async function getFile() { 15 const imagesDir = documentDirectory!
··· 3 getInfoAsync, 4 readDirectoryAsync, 5 } from 'expo-file-system/legacy' 6 + import {type ImagePickerResult} from 'expo-image-picker' 7 import ExpoImageCropTool, { 8 type OpenCropperOptions, 9 } from '@bsky.app/expo-image-crop-tool' 10 11 import {compressIfNeeded} from './manip' 12 import {type PickerImage} from './picker.shared' 13 14 async function getFile() { 15 const imagesDir = documentDirectory!
+3 -3
src/lib/media/picker.shared.ts
··· 5 } from 'expo-image-picker' 6 import {t} from '@lingui/macro' 7 8 - import {isIOS, isWeb} from '#/platform/detection' 9 import {type ImageMeta} from '#/state/gallery' 10 import * as Toast from '#/view/com/util/Toast' 11 import {VIDEO_MAX_DURATION_MS} from '../constants' 12 import {getDataUriSize} from './util' 13 ··· 53 quality: 1, 54 allowsMultipleSelection: true, 55 legacy: true, 56 - base64: isWeb, 57 - selectionLimit: isIOS ? selectionCountRemaining : undefined, 58 preferredAssetRepresentationMode: 59 UIImagePickerPreferredAssetRepresentationMode.Automatic, 60 videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000,
··· 5 } from 'expo-image-picker' 6 import {t} from '@lingui/macro' 7 8 import {type ImageMeta} from '#/state/gallery' 9 import * as Toast from '#/view/com/util/Toast' 10 + import {IS_IOS, IS_WEB} from '#/env' 11 import {VIDEO_MAX_DURATION_MS} from '../constants' 12 import {getDataUriSize} from './util' 13 ··· 53 quality: 1, 54 allowsMultipleSelection: true, 55 legacy: true, 56 + base64: IS_WEB, 57 + selectionLimit: IS_IOS ? selectionCountRemaining : undefined, 58 preferredAssetRepresentationMode: 59 UIImagePickerPreferredAssetRepresentationMode.Automatic, 60 videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000,
+2 -2
src/lib/media/save-image.ios.ts
··· 2 import {msg} from '@lingui/macro' 3 import {useLingui} from '@lingui/react' 4 5 - import {isNative} from '#/platform/detection' 6 import * as Toast from '#/components/Toast' 7 import {saveImageToMediaLibrary} from './manip' 8 9 /** ··· 16 const {_} = useLingui() 17 return useCallback( 18 async (uri: string) => { 19 - if (!isNative) { 20 throw new Error('useSaveImageToMediaLibrary is native only') 21 } 22
··· 2 import {msg} from '@lingui/macro' 3 import {useLingui} from '@lingui/react' 4 5 import * as Toast from '#/components/Toast' 6 + import {IS_NATIVE} from '#/env' 7 import {saveImageToMediaLibrary} from './manip' 8 9 /** ··· 16 const {_} = useLingui() 17 return useCallback( 18 async (uri: string) => { 19 + if (!IS_NATIVE) { 20 throw new Error('useSaveImageToMediaLibrary is native only') 21 } 22
+2 -2
src/lib/media/save-image.ts
··· 3 import {msg} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 - import {isNative} from '#/platform/detection' 7 import * as Toast from '#/components/Toast' 8 import {saveImageToMediaLibrary} from './manip' 9 10 /** ··· 18 }) 19 return useCallback( 20 async (uri: string) => { 21 - if (!isNative) { 22 throw new Error('useSaveImageToMediaLibrary is native only') 23 } 24
··· 3 import {msg} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import * as Toast from '#/components/Toast' 7 + import {IS_NATIVE} from '#/env' 8 import {saveImageToMediaLibrary} from './manip' 9 10 /** ··· 18 }) 19 return useCallback( 20 async (uri: string) => { 21 + if (!IS_NATIVE) { 22 throw new Error('useSaveImageToMediaLibrary is native only') 23 } 24
+5 -5
src/lib/notifications/notifications.ts
··· 13 } from '#/lib/constants' 14 import {logger as notyLogger} from '#/lib/notifications/util' 15 import {isNetworkError} from '#/lib/strings/errors' 16 - import {isNative} from '#/platform/detection' 17 import {type SessionAccount, useAgent, useSession} from '#/state/session' 18 import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' 19 import {useAgeAssurance} from '#/ageAssurance' 20 import {IS_DEV} from '#/env' 21 22 /** ··· 140 }: { 141 isAgeRestricted?: boolean 142 } = {}) => { 143 - if (!isNative || IS_DEV) return 144 145 /** 146 * This will also fire the listener added via `addPushTokenListener`. That ··· 236 const permissions = await Notifications.getPermissionsAsync() 237 238 if ( 239 - !isNative || 240 permissions?.status === 'granted' || 241 (permissions?.status === 'denied' && !permissions.canAskAgain) 242 ) { ··· 277 } 278 279 export async function decrementBadgeCount(by: number) { 280 - if (!isNative) return 281 282 let count = await getBadgeCountAsync() 283 count -= by ··· 295 } 296 297 export async function unregisterPushToken(agents: AtpAgent[]) { 298 - if (!isNative) return 299 300 try { 301 const token = await getPushToken()
··· 13 } from '#/lib/constants' 14 import {logger as notyLogger} from '#/lib/notifications/util' 15 import {isNetworkError} from '#/lib/strings/errors' 16 import {type SessionAccount, useAgent, useSession} from '#/state/session' 17 import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' 18 import {useAgeAssurance} from '#/ageAssurance' 19 + import {IS_NATIVE} from '#/env' 20 import {IS_DEV} from '#/env' 21 22 /** ··· 140 }: { 141 isAgeRestricted?: boolean 142 } = {}) => { 143 + if (!IS_NATIVE || IS_DEV) return 144 145 /** 146 * This will also fire the listener added via `addPushTokenListener`. That ··· 236 const permissions = await Notifications.getPermissionsAsync() 237 238 if ( 239 + !IS_NATIVE || 240 permissions?.status === 'granted' || 241 (permissions?.status === 'denied' && !permissions.canAskAgain) 242 ) { ··· 277 } 278 279 export async function decrementBadgeCount(by: number) { 280 + if (!IS_NATIVE) return 281 282 let count = await getBadgeCountAsync() 283 count -= by ··· 295 } 296 297 export async function unregisterPushToken(agents: AtpAgent[]) { 298 + if (!IS_NATIVE) return 299 300 try { 301 const token = await getPushToken()
+3 -3
src/lib/react-query.tsx
··· 9 } from '@tanstack/react-query-persist-client' 10 import type React from 'react' 11 12 - import {isNative, isWeb} from '#/platform/detection' 13 import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events' 14 import {PUBLIC_BSKY_SERVICE} from './constants' 15 16 declare global { 17 interface Window { ··· 88 }, 2000) 89 90 focusManager.setEventListener(onFocus => { 91 - if (isNative) { 92 const subscription = AppState.addEventListener( 93 'change', 94 (status: AppStateStatus) => { ··· 188 } 189 }) 190 useEffect(() => { 191 - if (isWeb) { 192 window.__TANSTACK_QUERY_CLIENT__ = queryClient 193 } 194 }, [queryClient])
··· 9 } from '@tanstack/react-query-persist-client' 10 import type React from 'react' 11 12 import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events' 13 import {PUBLIC_BSKY_SERVICE} from './constants' 14 + import {IS_NATIVE, IS_WEB} from '#/env' 15 16 declare global { 17 interface Window { ··· 88 }, 2000) 89 90 focusManager.setEventListener(onFocus => { 91 + if (IS_NATIVE) { 92 const subscription = AppState.addEventListener( 93 'change', 94 (status: AppStateStatus) => { ··· 188 } 189 }) 190 useEffect(() => { 191 + if (IS_WEB) { 192 window.__TANSTACK_QUERY_CLIENT__ = queryClient 193 } 194 }, [queryClient])
+4 -4
src/lib/sharing.ts
··· 4 // TODO: replace global i18n instance with one returned from useLingui -sfn 5 import {t} from '@lingui/macro' 6 7 - import {isAndroid, isIOS} from '#/platform/detection' 8 import * as Toast from '#/view/com/util/Toast' 9 10 /** 11 * This function shares a URL using the native Share API if available, or copies it to the clipboard ··· 14 * clipboard. 15 */ 16 export async function shareUrl(url: string) { 17 - if (isAndroid) { 18 await Share.share({message: url}) 19 - } else if (isIOS) { 20 await Share.share({url}) 21 } else { 22 // React Native Share is not supported by web. Web Share API ··· 34 * clipboard. 35 */ 36 export async function shareText(text: string) { 37 - if (isAndroid || isIOS) { 38 await Share.share({message: text}) 39 } else { 40 await setStringAsync(text)
··· 4 // TODO: replace global i18n instance with one returned from useLingui -sfn 5 import {t} from '@lingui/macro' 6 7 import * as Toast from '#/view/com/util/Toast' 8 + import {IS_ANDROID, IS_IOS} from '#/env' 9 10 /** 11 * This function shares a URL using the native Share API if available, or copies it to the clipboard ··· 14 * clipboard. 15 */ 16 export async function shareUrl(url: string) { 17 + if (IS_ANDROID) { 18 await Share.share({message: url}) 19 + } else if (IS_IOS) { 20 await Share.share({url}) 21 } else { 22 // React Native Share is not supported by web. Web Share API ··· 34 * clipboard. 35 */ 36 export async function shareText(text: string) { 37 + if (IS_ANDROID || IS_IOS) { 38 await Share.share({message: text}) 39 } else { 40 await setStringAsync(text)
+1 -1
src/lib/statsig/gates.ts
··· 3 | 'alt_share_icon' 4 | 'debug_show_feedcontext' 5 | 'debug_subscriptions' 6 | 'disable_onboarding_find_contacts' 7 | 'disable_settings_find_contacts' 8 | 'explore_show_suggested_feeds' 9 | 'feed_reply_button_open_thread' 10 | 'is_bsky_team_member' // special, do not remove 11 - | 'live_now_beta' 12 | 'old_postonboarding' 13 | 'onboarding_add_video_feed' 14 | 'onboarding_suggested_starterpacks'
··· 3 | 'alt_share_icon' 4 | 'debug_show_feedcontext' 5 | 'debug_subscriptions' 6 + | 'disable_live_now_beta' 7 | 'disable_onboarding_find_contacts' 8 | 'disable_settings_find_contacts' 9 | 'explore_show_suggested_feeds' 10 | 'feed_reply_button_open_thread' 11 | 'is_bsky_team_member' // special, do not remove 12 | 'old_postonboarding' 13 | 'onboarding_add_video_feed' 14 | 'onboarding_suggested_starterpacks'
+2 -3
src/lib/statsig/statsig.tsx
··· 5 6 import {logger} from '#/logger' 7 import {type MetricEvents} from '#/logger/metrics' 8 - import {isWeb} from '#/platform/detection' 9 import * as persisted from '#/state/persisted' 10 - // import {useSession} from '../../state/session' 11 import * as env from '#/env' 12 import {device} from '#/storage' 13 import {timeout} from '../async/timeout' ··· 38 39 let refSrc = '' 40 let refUrl = '' 41 - if (isWeb && typeof window !== 'undefined') { 42 const params = new URLSearchParams(window.location.search) 43 refSrc = params.get('ref_src') ?? '' 44 refUrl = decodeURIComponent(params.get('ref_url') ?? '')
··· 5 6 import {logger} from '#/logger' 7 import {type MetricEvents} from '#/logger/metrics' 8 import * as persisted from '#/state/persisted' 9 + import {IS_WEB} from '#/env' 10 import * as env from '#/env' 11 import {device} from '#/storage' 12 import {timeout} from '../async/timeout' ··· 37 38 let refSrc = '' 39 let refUrl = '' 40 + if (IS_WEB && typeof window !== 'undefined') { 41 const params = new URLSearchParams(window.location.search) 42 refSrc = params.get('ref_src') ?? '' 43 refUrl = decodeURIComponent(params.get('ref_url') ?? '')
+5 -6
src/lib/strings/embed-player.ts
··· 1 import {Dimensions} from 'react-native' 2 3 - import {isSafari} from '#/lib/browser' 4 - import {isWeb} from '#/platform/detection' 5 6 const {height: SCREEN_HEIGHT} = Dimensions.get('window') 7 8 - const IFRAME_HOST = isWeb 9 ? // @ts-ignore only for web 10 window.location.host === 'localhost:8100' 11 ? 'http://localhost:8100' ··· 135 urlp.hostname === 'www.twitch.tv' || 136 urlp.hostname === 'm.twitch.tv' 137 ) { 138 - const parent = isWeb 139 ? // @ts-ignore only for web 140 window.location.hostname 141 : 'localhost' ··· 570 width: Number(w), 571 } 572 573 - if (isWeb) { 574 - if (isSafari) { 575 id = id.replace('AAAAC', 'AAAP1') 576 filename = filename.replace('.gif', '.mp4') 577 } else {
··· 1 import {Dimensions} from 'react-native' 2 3 + import {IS_WEB, IS_WEB_SAFARI} from '#/env' 4 5 const {height: SCREEN_HEIGHT} = Dimensions.get('window') 6 7 + const IFRAME_HOST = IS_WEB 8 ? // @ts-ignore only for web 9 window.location.host === 'localhost:8100' 10 ? 'http://localhost:8100' ··· 134 urlp.hostname === 'www.twitch.tv' || 135 urlp.hostname === 'm.twitch.tv' 136 ) { 137 + const parent = IS_WEB 138 ? // @ts-ignore only for web 139 window.location.hostname 140 : 'localhost' ··· 569 width: Number(w), 570 } 571 572 + if (IS_WEB) { 573 + if (IS_WEB_SAFARI) { 574 id = id.replace('AAAAC', 'AAAP1') 575 filename = filename.replace('.gif', '.mp4') 576 } else {
+2 -2
src/lib/styles.ts
··· 5 type TextStyle, 6 } from 'react-native' 7 8 - import {isWeb} from '#/platform/detection' 9 import {type Theme, type TypographyVariant} from './ThemeContext' 10 11 // 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest ··· 186 // dimensions 187 w100pct: {width: '100%'}, 188 h100pct: {height: '100%'}, 189 - hContentRegion: isWeb ? {minHeight: '100%'} : {height: '100%'}, 190 window: { 191 width: Dimensions.get('window').width, 192 height: Dimensions.get('window').height,
··· 5 type TextStyle, 6 } from 'react-native' 7 8 + import {IS_WEB} from '#/env' 9 import {type Theme, type TypographyVariant} from './ThemeContext' 10 11 // 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest ··· 186 // dimensions 187 w100pct: {width: '100%'}, 188 h100pct: {height: '100%'}, 189 + hContentRegion: IS_WEB ? {minHeight: '100%'} : {height: '100%'}, 190 window: { 191 width: Dimensions.get('window').width, 192 height: Dimensions.get('window').height,
+139 -139
src/locale/locales/en/messages.po
··· 18 msgid "\"{interestsDisplayName}\" category (active)" 19 msgstr "" 20 21 - #: src/screens/Messages/components/ChatListItem.tsx:163 22 msgid "(contains embedded content)" 23 msgstr "" 24 ··· 163 msgid "{0} is not available" 164 msgstr "" 165 166 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:232 167 msgid "{0} joined this week" 168 msgstr "" 169 ··· 180 msgid "{0} reacted {1}" 181 msgstr "" 182 183 - #: src/screens/Messages/components/ChatListItem.tsx:234 184 msgid "{0} reacted {1} to {2}" 185 msgstr "" 186 ··· 192 msgid "{0}, a list by {1}" 193 msgstr "" 194 195 - #: src/view/com/util/UserAvatar.tsx:578 196 - #: src/view/com/util/UserAvatar.tsx:596 197 msgid "{0}'s avatar" 198 msgstr "" 199 ··· 540 msgstr "" 541 542 #. If last message does not contain text, fall back to "{user} reacted to {a message}" 543 - #: src/screens/Messages/components/ChatListItem.tsx:213 544 msgid "a message" 545 msgstr "" 546 ··· 714 msgid "Add a content warning" 715 msgstr "" 716 717 - #: src/components/live/GoLiveDialog.tsx:103 718 msgid "Add a temporary live status to your profile. When someone clicks on your avatar, they’ll see information about your live event." 719 msgstr "" 720 ··· 738 msgid "Add alt text" 739 msgstr "" 740 741 - #: src/view/com/composer/videos/SubtitleDialog.tsx:110 742 msgid "Add alt text (optional)" 743 msgstr "" 744 ··· 826 msgid "Add this feed to your feeds" 827 msgstr "" 828 829 - #: src/view/com/profile/ProfileMenu.tsx:340 830 - #: src/view/com/profile/ProfileMenu.tsx:343 831 msgid "Add to lists" 832 msgstr "" 833 ··· 836 msgstr "" 837 838 #: src/components/dialogs/StarterPackDialog.tsx:176 839 - #: src/view/com/profile/ProfileMenu.tsx:331 840 - #: src/view/com/profile/ProfileMenu.tsx:334 841 msgid "Add to starter packs" 842 msgstr "" 843 ··· 1022 #: src/view/com/composer/GifAltText.tsx:154 1023 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:117 1024 #: src/view/com/composer/videos/SubtitleDialog.tsx:40 1025 - #: src/view/com/composer/videos/SubtitleDialog.tsx:55 1026 - #: src/view/com/composer/videos/SubtitleDialog.tsx:105 1027 #: src/view/com/composer/videos/SubtitleDialog.tsx:109 1028 msgid "Alt text" 1029 msgstr "" 1030 ··· 1036 msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." 1037 msgstr "" 1038 1039 - #: src/view/com/composer/videos/SubtitleDialog.tsx:133 1040 msgid "Alt text must be less than {MAX_ALT_TEXT} characters." 1041 msgstr "" 1042 ··· 1054 msgid "An error has occurred" 1055 msgstr "" 1056 1057 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:422 1058 msgid "An error occurred" 1059 msgstr "" 1060 ··· 1151 msgid "An mockup of a iPhone showing the Bluesky app open to the profile of a verified user with a blue checkmark next to their display name." 1152 msgstr "" 1153 1154 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:166 1155 msgid "An unknown error occurred." 1156 msgstr "" 1157 ··· 1476 1477 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:818 1478 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 1479 - #: src/view/com/profile/ProfileMenu.tsx:548 1480 msgid "Block" 1481 msgstr "" 1482 ··· 1486 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:705 1487 #: src/screens/Messages/components/RequestButtons.tsx:144 1488 #: src/screens/Messages/components/RequestButtons.tsx:146 1489 - #: src/view/com/profile/ProfileMenu.tsx:454 1490 - #: src/view/com/profile/ProfileMenu.tsx:461 1491 msgid "Block account" 1492 msgstr "" 1493 1494 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:813 1495 - #: src/view/com/profile/ProfileMenu.tsx:531 1496 msgid "Block Account?" 1497 msgstr "" 1498 ··· 1544 msgstr "" 1545 1546 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:815 1547 - #: src/view/com/profile/ProfileMenu.tsx:543 1548 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1549 msgstr "" 1550 ··· 1560 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1561 msgstr "" 1562 1563 - #: src/view/com/profile/ProfileMenu.tsx:540 1564 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." 1565 msgstr "" 1566 ··· 1761 #: src/components/dialogs/InAppBrowserConsent.tsx:104 1762 #: src/components/dialogs/lists/CreateOrEditListDialog.tsx:274 1763 #: src/components/dialogs/lists/CreateOrEditListDialog.tsx:282 1764 - #: src/components/live/GoLiveDialog.tsx:243 1765 - #: src/components/live/GoLiveDialog.tsx:249 1766 #: src/components/Menu/index.tsx:352 1767 #: src/components/PostControls/RepostButton.tsx:209 1768 #: src/components/Prompt.tsx:144 1769 #: src/components/Prompt.tsx:146 1770 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:153 1771 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:158 1772 #: src/lib/media/picker.tsx:38 1773 #: src/screens/Deactivated.tsx:149 1774 #: src/screens/Profile/Header/EditProfileDialog.tsx:218 ··· 1821 msgid "Cannot interact with a blocked user" 1822 msgstr "" 1823 1824 - #: src/view/com/composer/videos/SubtitleDialog.tsx:148 1825 msgid "Captions (.vtt)" 1826 msgstr "" 1827 1828 #: src/view/com/composer/videos/SubtitleDialog.tsx:40 1829 - #: src/view/com/composer/videos/SubtitleDialog.tsx:55 1830 msgid "Captions & alt text" 1831 msgstr "" 1832 ··· 2431 msgid "Conversation deleted" 2432 msgstr "" 2433 2434 - #: src/screens/Messages/components/ChatListItem.tsx:199 2435 msgid "Conversation deleted" 2436 msgstr "" 2437 ··· 2465 msgid "Copy App Password" 2466 msgstr "" 2467 2468 - #: src/view/com/profile/ProfileMenu.tsx:491 2469 - #: src/view/com/profile/ProfileMenu.tsx:494 2470 msgid "Copy at:// URI" 2471 msgstr "" 2472 ··· 2481 msgstr "" 2482 2483 #: src/screens/Settings/components/ChangeHandleDialog.tsx:502 2484 - #: src/view/com/profile/ProfileMenu.tsx:500 2485 - #: src/view/com/profile/ProfileMenu.tsx:503 2486 msgid "Copy DID" 2487 msgstr "" 2488 ··· 2495 msgid "Copy link" 2496 msgstr "" 2497 2498 - #: src/components/StarterPack/ShareDialog.tsx:119 2499 msgid "Copy Link" 2500 msgstr "" 2501 ··· 2671 msgid "Create an account" 2672 msgstr "" 2673 2674 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:312 2675 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:319 2676 msgid "Create an account without using this starter pack" 2677 msgstr "" 2678 ··· 2976 msgid "Disable replies entirely" 2977 msgstr "" 2978 2979 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:388 2980 msgid "Disable subtitles" 2981 msgstr "" 2982 ··· 3119 #: src/view/com/composer/labels/LabelsBtn.tsx:218 3120 #: src/view/com/composer/labels/LabelsBtn.tsx:225 3121 #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:303 3122 - #: src/view/com/composer/videos/SubtitleDialog.tsx:184 3123 - #: src/view/com/composer/videos/SubtitleDialog.tsx:195 3124 msgid "Done" 3125 msgstr "" 3126 ··· 3146 msgid "Double tap to like" 3147 msgstr "" 3148 3149 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:330 3150 msgid "Download Bluesky" 3151 msgstr "" 3152 ··· 3221 msgid "Edit" 3222 msgstr "" 3223 3224 - #: src/view/com/util/UserAvatar.tsx:440 3225 #: src/view/com/util/UserBanner.tsx:121 3226 msgid "Edit avatar" 3227 msgstr "" ··· 3251 msgid "Edit list details" 3252 msgstr "" 3253 3254 - #: src/view/com/profile/ProfileMenu.tsx:354 3255 - #: src/view/com/profile/ProfileMenu.tsx:374 3256 msgid "Edit live status" 3257 msgstr "" 3258 ··· 3408 msgid "Enable quote posts of this post" 3409 msgstr "" 3410 3411 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:389 3412 msgid "Enable subtitles" 3413 msgstr "" 3414 ··· 3436 msgid "End of feed" 3437 msgstr "" 3438 3439 - #: src/view/com/composer/videos/SubtitleDialog.tsx:174 3440 msgid "Ensure you have selected a language for each subtitle file." 3441 msgstr "" 3442 ··· 3459 msgid "Enter code" 3460 msgstr "" 3461 3462 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:407 3463 msgid "Enter fullscreen" 3464 msgstr "" 3465 ··· 3529 msgid "Error occurred while saving file" 3530 msgstr "" 3531 3532 - #: src/screens/Signup/StepCaptcha/index.tsx:123 3533 msgid "Error receiving captcha response." 3534 msgstr "" 3535 ··· 3565 msgid "Excludes users you follow" 3566 msgstr "" 3567 3568 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:406 3569 msgid "Exit fullscreen" 3570 msgstr "" 3571 ··· 4121 msgid "Follow 7 accounts" 4122 msgstr "" 4123 4124 - #: src/view/com/profile/ProfileMenu.tsx:310 4125 - #: src/view/com/profile/ProfileMenu.tsx:321 4126 msgid "Follow account" 4127 msgstr "" 4128 ··· 4433 msgid "Go Home" 4434 msgstr "" 4435 4436 - #: src/view/com/profile/ProfileMenu.tsx:355 4437 - #: src/view/com/profile/ProfileMenu.tsx:376 4438 msgid "Go live" 4439 msgstr "" 4440 4441 - #: src/components/live/GoLiveDialog.tsx:95 4442 - #: src/components/live/GoLiveDialog.tsx:100 4443 - #: src/components/live/GoLiveDialog.tsx:228 4444 - #: src/components/live/GoLiveDialog.tsx:237 4445 msgid "Go Live" 4446 msgstr "" 4447 4448 - #: src/view/com/profile/ProfileMenu.tsx:352 4449 - #: src/view/com/profile/ProfileMenu.tsx:372 4450 msgid "Go live (disabled)" 4451 msgstr "" 4452 4453 - #: src/components/live/GoLiveDialog.tsx:170 4454 msgid "Go live for" 4455 msgstr "" 4456 ··· 4465 msgid "Go to account settings" 4466 msgstr "" 4467 4468 - #: src/screens/Messages/components/ChatListItem.tsx:364 4469 msgid "Go to conversation with {0}" 4470 msgstr "" 4471 ··· 4644 msgid "Hide" 4645 msgstr "" 4646 4647 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:140 4648 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:147 4649 msgid "Hide all events" 4650 msgstr "" 4651 ··· 4676 msgid "Hide this card" 4677 msgstr "" 4678 4679 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:128 4680 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:135 4681 msgid "Hide this event" 4682 msgstr "" 4683 ··· 4830 msgid "If you believe your birthdate is incorrect, you can update it by <0>clicking here</0>." 4831 msgstr "" 4832 4833 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:119 4834 msgid "If you choose to hide all events, you can always re-enable them from <0>Settings → Content & Media</0>." 4835 msgstr "" 4836 ··· 5093 msgid "Jobs" 5094 msgstr "" 5095 5096 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:210 5097 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:216 5098 #: src/screens/StarterPack/StarterPackScreen.tsx:464 5099 #: src/screens/StarterPack/StarterPackScreen.tsx:475 5100 msgid "Join Bluesky" ··· 5491 msgid "Live event happening now: {0}" 5492 msgstr "" 5493 5494 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:107 5495 msgid "Live event options" 5496 msgstr "" 5497 5498 - #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:111 5499 msgid "Live events appear occasionally when something exciting is happening. If you'd like, you can hide this particular event, or all events for this placement in your app interface." 5500 msgstr "" 5501 ··· 5505 5506 #: src/components/live/EditLiveDialog.tsx:147 5507 #: src/components/live/EditLiveDialog.tsx:151 5508 - #: src/components/live/GoLiveDialog.tsx:126 5509 - #: src/components/live/GoLiveDialog.tsx:130 5510 msgid "Live link" 5511 msgstr "" 5512 ··· 5665 msgid "Message deleted" 5666 msgstr "" 5667 5668 - #: src/screens/Messages/components/ChatListItem.tsx:200 5669 msgid "Message deleted" 5670 msgstr "" 5671 ··· 5805 msgstr "" 5806 5807 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 5808 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95 5809 msgctxt "video" 5810 msgid "Mute" 5811 msgstr "" ··· 5817 5818 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:686 5819 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:692 5820 - #: src/view/com/profile/ProfileMenu.tsx:433 5821 - #: src/view/com/profile/ProfileMenu.tsx:440 5822 msgid "Mute account" 5823 msgstr "" 5824 ··· 5948 msgstr "" 5949 5950 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:196 5951 - #: src/view/com/profile/ProfileMenu.tsx:388 5952 msgid "New" 5953 msgstr "" 5954 ··· 6149 msgid "No media yet" 6150 msgstr "" 6151 6152 - #: src/screens/Messages/components/ChatListItem.tsx:142 6153 msgid "No messages yet" 6154 msgstr "" 6155 ··· 6293 msgid "Not Found" 6294 msgstr "" 6295 6296 - #: src/view/com/profile/ProfileMenu.tsx:555 6297 msgid "Note about sharing" 6298 msgstr "" 6299 ··· 6475 msgid "Open camera" 6476 msgstr "" 6477 6478 - #: src/screens/Messages/components/ChatListItem.tsx:374 6479 - #: src/screens/Messages/components/ChatListItem.tsx:378 6480 msgid "Open conversation options" 6481 msgstr "" 6482 ··· 6623 msgid "Opens link {0}" 6624 msgstr "" 6625 6626 - #: src/view/com/util/UserAvatar.tsx:582 6627 msgid "Opens live status dialog" 6628 msgstr "" 6629 ··· 6640 msgstr "" 6641 6642 #: src/view/com/notifications/NotificationFeedItem.tsx:1027 6643 - #: src/view/com/util/UserAvatar.tsx:600 6644 msgid "Opens this profile" 6645 msgstr "" 6646 ··· 6760 6761 #: src/components/Post/Embed/ExternalEmbed/Gif.tsx:44 6762 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:137 6763 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:369 6764 msgid "Pause" 6765 msgstr "" 6766 6767 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:320 6768 msgid "Pause video" 6769 msgstr "" 6770 ··· 6871 6872 #: src/components/Post/Embed/ExternalEmbed/Gif.tsx:44 6873 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:137 6874 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:370 6875 msgid "Play" 6876 msgstr "" 6877 ··· 6880 msgstr "" 6881 6882 #: src/components/Post/Embed/VideoEmbed/index.tsx:115 6883 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:321 6884 msgid "Play video" 6885 msgstr "" 6886 ··· 7065 msgid "Porn" 7066 msgstr "" 7067 7068 - #: src/screens/PostThread/index.tsx:532 7069 msgctxt "description" 7070 msgid "Post" 7071 msgstr "" ··· 7536 msgid "Remove attachment" 7537 msgstr "" 7538 7539 - #: src/view/com/util/UserAvatar.tsx:499 7540 - #: src/view/com/util/UserAvatar.tsx:502 7541 msgid "Remove Avatar" 7542 msgstr "" 7543 ··· 7608 msgid "Remove repost" 7609 msgstr "" 7610 7611 - #: src/view/com/composer/videos/SubtitleDialog.tsx:278 7612 msgid "Remove subtitle file" 7613 msgstr "" 7614 ··· 7627 7628 #: src/components/verification/VerificationRemovePrompt.tsx:46 7629 #: src/components/verification/VerificationsDialog.tsx:252 7630 - #: src/view/com/profile/ProfileMenu.tsx:406 7631 - #: src/view/com/profile/ProfileMenu.tsx:409 7632 msgid "Remove verification" 7633 msgstr "" 7634 ··· 7763 msgid "Report" 7764 msgstr "" 7765 7766 - #: src/view/com/profile/ProfileMenu.tsx:473 7767 - #: src/view/com/profile/ProfileMenu.tsx:476 7768 msgid "Report account" 7769 msgstr "" 7770 ··· 8074 msgid "Save changes" 8075 msgstr "" 8076 8077 - #: src/components/StarterPack/ShareDialog.tsx:138 8078 - #: src/components/StarterPack/ShareDialog.tsx:144 8079 msgid "Save image" 8080 msgstr "" 8081 ··· 8223 msgid "Search my posts" 8224 msgstr "" 8225 8226 - #: src/view/com/profile/ProfileMenu.tsx:289 8227 - #: src/view/com/profile/ProfileMenu.tsx:292 8228 msgid "Search posts" 8229 msgstr "" 8230 ··· 8350 msgid "Select content languages" 8351 msgstr "" 8352 8353 - #: src/components/live/GoLiveDialog.tsx:175 8354 msgid "Select duration" 8355 msgstr "" 8356 ··· 8384 msgid "Select language" 8385 msgstr "" 8386 8387 - #: src/view/com/composer/videos/SubtitleDialog.tsx:265 8388 msgid "Select language..." 8389 msgstr "" 8390 ··· 8642 msgid "Share a fun fact!" 8643 msgstr "" 8644 8645 - #: src/view/com/profile/ProfileMenu.tsx:560 8646 msgid "Share anyway" 8647 msgstr "" 8648 ··· 8654 #: src/components/dialogs/LinkWarning.tsx:96 8655 #: src/components/dialogs/LinkWarning.tsx:104 8656 #: src/components/StarterPack/ShareDialog.tsx:113 8657 - #: src/components/StarterPack/ShareDialog.tsx:119 8658 msgid "Share link" 8659 msgstr "" 8660 ··· 8667 msgid "Share post at:// URI" 8668 msgstr "" 8669 8670 - #: src/components/StarterPack/ShareDialog.tsx:123 8671 - #: src/components/StarterPack/ShareDialog.tsx:133 8672 msgid "Share QR code" 8673 msgstr "" 8674 ··· 8974 msgid "Someone reacted {0}" 8975 msgstr "" 8976 8977 - #: src/screens/Messages/components/ChatListItem.tsx:244 8978 msgid "Someone reacted {0} to {1}" 8979 msgstr "" 8980 ··· 9404 9405 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:186 9406 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:391 9407 - #: src/view/com/profile/ProfileMenu.tsx:536 9408 msgid "The account will be able to interact with you after unblocking." 9409 msgstr "" 9410 ··· 9442 msgid "The Discover feed now knows what you like" 9443 msgstr "" 9444 9445 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:333 9446 msgid "The experience is better in the app. Download Bluesky now and we'll pick back up where you left off." 9447 msgstr "" 9448 ··· 9458 msgid "The following labels were applied to your content." 9459 msgstr "" 9460 9461 - #: src/components/live/GoLiveDialog.tsx:157 9462 msgid "The following services are enabled for your account: {allowedServices}" 9463 msgstr "" 9464 ··· 9551 msgstr "" 9552 9553 #: src/screens/Search/Explore.tsx:999 9554 - #: src/view/com/posts/PostFeed.tsx:759 9555 msgid "There was an issue fetching posts. Tap here to try again." 9556 msgstr "" 9557 ··· 9702 msgid "This content is not viewable without a Bluesky account." 9703 msgstr "" 9704 9705 - #: src/screens/Messages/components/ChatListItem.tsx:366 9706 msgid "This conversation is with a deleted or a deactivated account. Press for options" 9707 msgstr "" 9708 ··· 9757 msgstr "" 9758 9759 #: src/components/live/EditLiveDialog.tsx:176 9760 - #: src/components/live/GoLiveDialog.tsx:150 9761 msgid "This is not a valid link" 9762 msgstr "" 9763 ··· 9817 msgid "This post's author has disabled quote posts." 9818 msgstr "" 9819 9820 - #: src/view/com/profile/ProfileMenu.tsx:557 9821 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in." 9822 msgstr "" 9823 ··· 10090 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:394 10091 #: src/screens/ProfileList/components/Header.tsx:171 10092 #: src/screens/ProfileList/components/Header.tsx:178 10093 - #: src/view/com/profile/ProfileMenu.tsx:548 10094 msgid "Unblock" 10095 msgstr "" 10096 ··· 10101 10102 #: src/components/dms/ConvoMenu.tsx:261 10103 #: src/components/dms/ConvoMenu.tsx:264 10104 - #: src/view/com/profile/ProfileMenu.tsx:453 10105 - #: src/view/com/profile/ProfileMenu.tsx:459 10106 msgid "Unblock account" 10107 msgstr "" 10108 10109 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:184 10110 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:389 10111 - #: src/view/com/profile/ProfileMenu.tsx:530 10112 msgid "Unblock Account?" 10113 msgstr "" 10114 ··· 10141 msgid "Unfollow {0}" 10142 msgstr "" 10143 10144 - #: src/view/com/profile/ProfileMenu.tsx:309 10145 - #: src/view/com/profile/ProfileMenu.tsx:319 10146 msgid "Unfollow account" 10147 msgstr "" 10148 ··· 10184 msgstr "" 10185 10186 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152 10187 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:94 10188 msgctxt "video" 10189 msgid "Unmute" 10190 msgstr "" ··· 10201 10202 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:685 10203 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:691 10204 - #: src/view/com/profile/ProfileMenu.tsx:432 10205 - #: src/view/com/profile/ProfileMenu.tsx:438 10206 msgid "Unmute account" 10207 msgstr "" 10208 ··· 10220 msgid "Unmute thread" 10221 msgstr "" 10222 10223 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:318 10224 msgid "Unmute video" 10225 msgstr "" 10226 ··· 10339 msgid "Upload a text file to:" 10340 msgstr "" 10341 10342 - #: src/view/com/util/UserAvatar.tsx:470 10343 - #: src/view/com/util/UserAvatar.tsx:473 10344 #: src/view/com/util/UserBanner.tsx:159 10345 #: src/view/com/util/UserBanner.tsx:162 10346 msgid "Upload from Camera" 10347 msgstr "" 10348 10349 - #: src/view/com/util/UserAvatar.tsx:487 10350 #: src/view/com/util/UserBanner.tsx:176 10351 msgid "Upload from Files" 10352 msgstr "" 10353 10354 - #: src/view/com/util/UserAvatar.tsx:481 10355 - #: src/view/com/util/UserAvatar.tsx:485 10356 #: src/view/com/util/UserBanner.tsx:170 10357 #: src/view/com/util/UserBanner.tsx:174 10358 msgid "Upload from Library" ··· 10510 10511 #: src/components/verification/VerificationCreatePrompt.tsx:84 10512 #: src/components/verification/VerificationCreatePrompt.tsx:86 10513 - #: src/view/com/profile/ProfileMenu.tsx:416 10514 - #: src/view/com/profile/ProfileMenu.tsx:419 10515 msgid "Verify account" 10516 msgstr "" 10517 ··· 10626 msgid "Video not found." 10627 msgstr "" 10628 10629 - #: src/view/com/composer/videos/SubtitleDialog.tsx:102 10630 msgid "Video settings" 10631 msgstr "" 10632 ··· 11120 msgstr "" 11121 11122 #: src/components/live/EditLiveDialog.tsx:152 11123 - #: src/components/live/GoLiveDialog.tsx:131 11124 msgid "www.mylivestream.tv" 11125 msgstr "" 11126 ··· 11464 msgid "You reacted {0}" 11465 msgstr "" 11466 11467 - #: src/screens/Messages/components/ChatListItem.tsx:221 11468 msgid "You reacted {0} to {1}" 11469 msgstr "" 11470 ··· 11494 msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." 11495 msgstr "" 11496 11497 - #: src/screens/Messages/components/ChatListItem.tsx:157 11498 msgid "You: {0}" 11499 msgstr "" 11500 11501 - #: src/screens/Messages/components/ChatListItem.tsx:186 11502 msgid "You: {defaultEmbeddedContentMessage}" 11503 msgstr "" 11504 11505 - #: src/screens/Messages/components/ChatListItem.tsx:179 11506 msgid "You: {short}" 11507 msgstr "" 11508 ··· 11514 msgid "You'll follow the suggested users once you finish creating your account!" 11515 msgstr "" 11516 11517 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:245 11518 msgid "You'll follow these people and {0} others" 11519 msgstr "" 11520 11521 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:243 11522 msgid "You'll follow these people right away" 11523 msgstr "" 11524 ··· 11530 msgid "You'll start receiving notifications for {0}!" 11531 msgstr "" 11532 11533 - #: src/screens/StarterPack/StarterPackLandingScreen.tsx:283 11534 msgid "You'll stay updated with these feeds" 11535 msgstr "" 11536
··· 18 msgid "\"{interestsDisplayName}\" category (active)" 19 msgstr "" 20 21 + #: src/screens/Messages/components/ChatListItem.tsx:162 22 msgid "(contains embedded content)" 23 msgstr "" 24 ··· 163 msgid "{0} is not available" 164 msgstr "" 165 166 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:231 167 msgid "{0} joined this week" 168 msgstr "" 169 ··· 180 msgid "{0} reacted {1}" 181 msgstr "" 182 183 + #: src/screens/Messages/components/ChatListItem.tsx:233 184 msgid "{0} reacted {1} to {2}" 185 msgstr "" 186 ··· 192 msgid "{0}, a list by {1}" 193 msgstr "" 194 195 + #: src/view/com/util/UserAvatar.tsx:577 196 + #: src/view/com/util/UserAvatar.tsx:595 197 msgid "{0}'s avatar" 198 msgstr "" 199 ··· 540 msgstr "" 541 542 #. If last message does not contain text, fall back to "{user} reacted to {a message}" 543 + #: src/screens/Messages/components/ChatListItem.tsx:212 544 msgid "a message" 545 msgstr "" 546 ··· 714 msgid "Add a content warning" 715 msgstr "" 716 717 + #: src/components/live/GoLiveDialog.tsx:106 718 msgid "Add a temporary live status to your profile. When someone clicks on your avatar, they’ll see information about your live event." 719 msgstr "" 720 ··· 738 msgid "Add alt text" 739 msgstr "" 740 741 + #: src/view/com/composer/videos/SubtitleDialog.tsx:114 742 msgid "Add alt text (optional)" 743 msgstr "" 744 ··· 826 msgid "Add this feed to your feeds" 827 msgstr "" 828 829 + #: src/view/com/profile/ProfileMenu.tsx:342 830 + #: src/view/com/profile/ProfileMenu.tsx:345 831 msgid "Add to lists" 832 msgstr "" 833 ··· 836 msgstr "" 837 838 #: src/components/dialogs/StarterPackDialog.tsx:176 839 + #: src/view/com/profile/ProfileMenu.tsx:333 840 + #: src/view/com/profile/ProfileMenu.tsx:336 841 msgid "Add to starter packs" 842 msgstr "" 843 ··· 1022 #: src/view/com/composer/GifAltText.tsx:154 1023 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:117 1024 #: src/view/com/composer/videos/SubtitleDialog.tsx:40 1025 + #: src/view/com/composer/videos/SubtitleDialog.tsx:58 1026 #: src/view/com/composer/videos/SubtitleDialog.tsx:109 1027 + #: src/view/com/composer/videos/SubtitleDialog.tsx:113 1028 msgid "Alt text" 1029 msgstr "" 1030 ··· 1036 msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." 1037 msgstr "" 1038 1039 + #: src/view/com/composer/videos/SubtitleDialog.tsx:137 1040 msgid "Alt text must be less than {MAX_ALT_TEXT} characters." 1041 msgstr "" 1042 ··· 1054 msgid "An error has occurred" 1055 msgstr "" 1056 1057 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:421 1058 msgid "An error occurred" 1059 msgstr "" 1060 ··· 1151 msgid "An mockup of a iPhone showing the Bluesky app open to the profile of a verified user with a blue checkmark next to their display name." 1152 msgstr "" 1153 1154 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:164 1155 msgid "An unknown error occurred." 1156 msgstr "" 1157 ··· 1476 1477 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:818 1478 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 1479 + #: src/view/com/profile/ProfileMenu.tsx:550 1480 msgid "Block" 1481 msgstr "" 1482 ··· 1486 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:705 1487 #: src/screens/Messages/components/RequestButtons.tsx:144 1488 #: src/screens/Messages/components/RequestButtons.tsx:146 1489 + #: src/view/com/profile/ProfileMenu.tsx:456 1490 + #: src/view/com/profile/ProfileMenu.tsx:463 1491 msgid "Block account" 1492 msgstr "" 1493 1494 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:813 1495 + #: src/view/com/profile/ProfileMenu.tsx:533 1496 msgid "Block Account?" 1497 msgstr "" 1498 ··· 1544 msgstr "" 1545 1546 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:815 1547 + #: src/view/com/profile/ProfileMenu.tsx:545 1548 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1549 msgstr "" 1550 ··· 1560 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1561 msgstr "" 1562 1563 + #: src/view/com/profile/ProfileMenu.tsx:542 1564 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." 1565 msgstr "" 1566 ··· 1761 #: src/components/dialogs/InAppBrowserConsent.tsx:104 1762 #: src/components/dialogs/lists/CreateOrEditListDialog.tsx:274 1763 #: src/components/dialogs/lists/CreateOrEditListDialog.tsx:282 1764 + #: src/components/live/GoLiveDialog.tsx:246 1765 + #: src/components/live/GoLiveDialog.tsx:252 1766 #: src/components/Menu/index.tsx:352 1767 #: src/components/PostControls/RepostButton.tsx:209 1768 #: src/components/Prompt.tsx:144 1769 #: src/components/Prompt.tsx:146 1770 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:151 1771 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:156 1772 #: src/lib/media/picker.tsx:38 1773 #: src/screens/Deactivated.tsx:149 1774 #: src/screens/Profile/Header/EditProfileDialog.tsx:218 ··· 1821 msgid "Cannot interact with a blocked user" 1822 msgstr "" 1823 1824 + #: src/view/com/composer/videos/SubtitleDialog.tsx:152 1825 msgid "Captions (.vtt)" 1826 msgstr "" 1827 1828 #: src/view/com/composer/videos/SubtitleDialog.tsx:40 1829 + #: src/view/com/composer/videos/SubtitleDialog.tsx:56 1830 msgid "Captions & alt text" 1831 msgstr "" 1832 ··· 2431 msgid "Conversation deleted" 2432 msgstr "" 2433 2434 + #: src/screens/Messages/components/ChatListItem.tsx:198 2435 msgid "Conversation deleted" 2436 msgstr "" 2437 ··· 2465 msgid "Copy App Password" 2466 msgstr "" 2467 2468 + #: src/view/com/profile/ProfileMenu.tsx:493 2469 + #: src/view/com/profile/ProfileMenu.tsx:496 2470 msgid "Copy at:// URI" 2471 msgstr "" 2472 ··· 2481 msgstr "" 2482 2483 #: src/screens/Settings/components/ChangeHandleDialog.tsx:502 2484 + #: src/view/com/profile/ProfileMenu.tsx:502 2485 + #: src/view/com/profile/ProfileMenu.tsx:505 2486 msgid "Copy DID" 2487 msgstr "" 2488 ··· 2495 msgid "Copy link" 2496 msgstr "" 2497 2498 + #: src/components/StarterPack/ShareDialog.tsx:120 2499 msgid "Copy Link" 2500 msgstr "" 2501 ··· 2671 msgid "Create an account" 2672 msgstr "" 2673 2674 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:311 2675 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:318 2676 msgid "Create an account without using this starter pack" 2677 msgstr "" 2678 ··· 2976 msgid "Disable replies entirely" 2977 msgstr "" 2978 2979 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:387 2980 msgid "Disable subtitles" 2981 msgstr "" 2982 ··· 3119 #: src/view/com/composer/labels/LabelsBtn.tsx:218 3120 #: src/view/com/composer/labels/LabelsBtn.tsx:225 3121 #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:303 3122 + #: src/view/com/composer/videos/SubtitleDialog.tsx:188 3123 + #: src/view/com/composer/videos/SubtitleDialog.tsx:199 3124 msgid "Done" 3125 msgstr "" 3126 ··· 3146 msgid "Double tap to like" 3147 msgstr "" 3148 3149 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:329 3150 msgid "Download Bluesky" 3151 msgstr "" 3152 ··· 3221 msgid "Edit" 3222 msgstr "" 3223 3224 + #: src/view/com/util/UserAvatar.tsx:439 3225 #: src/view/com/util/UserBanner.tsx:121 3226 msgid "Edit avatar" 3227 msgstr "" ··· 3251 msgid "Edit list details" 3252 msgstr "" 3253 3254 + #: src/view/com/profile/ProfileMenu.tsx:356 3255 + #: src/view/com/profile/ProfileMenu.tsx:376 3256 msgid "Edit live status" 3257 msgstr "" 3258 ··· 3408 msgid "Enable quote posts of this post" 3409 msgstr "" 3410 3411 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:388 3412 msgid "Enable subtitles" 3413 msgstr "" 3414 ··· 3436 msgid "End of feed" 3437 msgstr "" 3438 3439 + #: src/view/com/composer/videos/SubtitleDialog.tsx:178 3440 msgid "Ensure you have selected a language for each subtitle file." 3441 msgstr "" 3442 ··· 3459 msgid "Enter code" 3460 msgstr "" 3461 3462 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:406 3463 msgid "Enter fullscreen" 3464 msgstr "" 3465 ··· 3529 msgid "Error occurred while saving file" 3530 msgstr "" 3531 3532 + #: src/screens/Signup/StepCaptcha/index.tsx:125 3533 msgid "Error receiving captcha response." 3534 msgstr "" 3535 ··· 3565 msgid "Excludes users you follow" 3566 msgstr "" 3567 3568 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:405 3569 msgid "Exit fullscreen" 3570 msgstr "" 3571 ··· 4121 msgid "Follow 7 accounts" 4122 msgstr "" 4123 4124 + #: src/view/com/profile/ProfileMenu.tsx:312 4125 + #: src/view/com/profile/ProfileMenu.tsx:323 4126 msgid "Follow account" 4127 msgstr "" 4128 ··· 4433 msgid "Go Home" 4434 msgstr "" 4435 4436 + #: src/view/com/profile/ProfileMenu.tsx:357 4437 + #: src/view/com/profile/ProfileMenu.tsx:378 4438 msgid "Go live" 4439 msgstr "" 4440 4441 + #: src/components/live/GoLiveDialog.tsx:98 4442 + #: src/components/live/GoLiveDialog.tsx:103 4443 + #: src/components/live/GoLiveDialog.tsx:231 4444 + #: src/components/live/GoLiveDialog.tsx:240 4445 msgid "Go Live" 4446 msgstr "" 4447 4448 + #: src/view/com/profile/ProfileMenu.tsx:354 4449 + #: src/view/com/profile/ProfileMenu.tsx:374 4450 msgid "Go live (disabled)" 4451 msgstr "" 4452 4453 + #: src/components/live/GoLiveDialog.tsx:173 4454 msgid "Go live for" 4455 msgstr "" 4456 ··· 4465 msgid "Go to account settings" 4466 msgstr "" 4467 4468 + #: src/screens/Messages/components/ChatListItem.tsx:363 4469 msgid "Go to conversation with {0}" 4470 msgstr "" 4471 ··· 4644 msgid "Hide" 4645 msgstr "" 4646 4647 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:138 4648 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:145 4649 msgid "Hide all events" 4650 msgstr "" 4651 ··· 4676 msgid "Hide this card" 4677 msgstr "" 4678 4679 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:126 4680 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:133 4681 msgid "Hide this event" 4682 msgstr "" 4683 ··· 4830 msgid "If you believe your birthdate is incorrect, you can update it by <0>clicking here</0>." 4831 msgstr "" 4832 4833 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:117 4834 msgid "If you choose to hide all events, you can always re-enable them from <0>Settings → Content & Media</0>." 4835 msgstr "" 4836 ··· 5093 msgid "Jobs" 5094 msgstr "" 5095 5096 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:209 5097 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:215 5098 #: src/screens/StarterPack/StarterPackScreen.tsx:464 5099 #: src/screens/StarterPack/StarterPackScreen.tsx:475 5100 msgid "Join Bluesky" ··· 5491 msgid "Live event happening now: {0}" 5492 msgstr "" 5493 5494 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:105 5495 msgid "Live event options" 5496 msgstr "" 5497 5498 + #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:109 5499 msgid "Live events appear occasionally when something exciting is happening. If you'd like, you can hide this particular event, or all events for this placement in your app interface." 5500 msgstr "" 5501 ··· 5505 5506 #: src/components/live/EditLiveDialog.tsx:147 5507 #: src/components/live/EditLiveDialog.tsx:151 5508 + #: src/components/live/GoLiveDialog.tsx:129 5509 + #: src/components/live/GoLiveDialog.tsx:133 5510 msgid "Live link" 5511 msgstr "" 5512 ··· 5665 msgid "Message deleted" 5666 msgstr "" 5667 5668 + #: src/screens/Messages/components/ChatListItem.tsx:199 5669 msgid "Message deleted" 5670 msgstr "" 5671 ··· 5805 msgstr "" 5806 5807 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 5808 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:97 5809 msgctxt "video" 5810 msgid "Mute" 5811 msgstr "" ··· 5817 5818 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:686 5819 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:692 5820 + #: src/view/com/profile/ProfileMenu.tsx:435 5821 + #: src/view/com/profile/ProfileMenu.tsx:442 5822 msgid "Mute account" 5823 msgstr "" 5824 ··· 5948 msgstr "" 5949 5950 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:196 5951 + #: src/view/com/profile/ProfileMenu.tsx:390 5952 msgid "New" 5953 msgstr "" 5954 ··· 6149 msgid "No media yet" 6150 msgstr "" 6151 6152 + #: src/screens/Messages/components/ChatListItem.tsx:141 6153 msgid "No messages yet" 6154 msgstr "" 6155 ··· 6293 msgid "Not Found" 6294 msgstr "" 6295 6296 + #: src/view/com/profile/ProfileMenu.tsx:557 6297 msgid "Note about sharing" 6298 msgstr "" 6299 ··· 6475 msgid "Open camera" 6476 msgstr "" 6477 6478 + #: src/screens/Messages/components/ChatListItem.tsx:373 6479 + #: src/screens/Messages/components/ChatListItem.tsx:377 6480 msgid "Open conversation options" 6481 msgstr "" 6482 ··· 6623 msgid "Opens link {0}" 6624 msgstr "" 6625 6626 + #: src/view/com/util/UserAvatar.tsx:581 6627 msgid "Opens live status dialog" 6628 msgstr "" 6629 ··· 6640 msgstr "" 6641 6642 #: src/view/com/notifications/NotificationFeedItem.tsx:1027 6643 + #: src/view/com/util/UserAvatar.tsx:599 6644 msgid "Opens this profile" 6645 msgstr "" 6646 ··· 6760 6761 #: src/components/Post/Embed/ExternalEmbed/Gif.tsx:44 6762 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:137 6763 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:368 6764 msgid "Pause" 6765 msgstr "" 6766 6767 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:319 6768 msgid "Pause video" 6769 msgstr "" 6770 ··· 6871 6872 #: src/components/Post/Embed/ExternalEmbed/Gif.tsx:44 6873 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:137 6874 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:369 6875 msgid "Play" 6876 msgstr "" 6877 ··· 6880 msgstr "" 6881 6882 #: src/components/Post/Embed/VideoEmbed/index.tsx:115 6883 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:320 6884 msgid "Play video" 6885 msgstr "" 6886 ··· 7065 msgid "Porn" 7066 msgstr "" 7067 7068 + #: src/screens/PostThread/index.tsx:531 7069 msgctxt "description" 7070 msgid "Post" 7071 msgstr "" ··· 7536 msgid "Remove attachment" 7537 msgstr "" 7538 7539 + #: src/view/com/util/UserAvatar.tsx:498 7540 + #: src/view/com/util/UserAvatar.tsx:501 7541 msgid "Remove Avatar" 7542 msgstr "" 7543 ··· 7608 msgid "Remove repost" 7609 msgstr "" 7610 7611 + #: src/view/com/composer/videos/SubtitleDialog.tsx:282 7612 msgid "Remove subtitle file" 7613 msgstr "" 7614 ··· 7627 7628 #: src/components/verification/VerificationRemovePrompt.tsx:46 7629 #: src/components/verification/VerificationsDialog.tsx:252 7630 + #: src/view/com/profile/ProfileMenu.tsx:408 7631 + #: src/view/com/profile/ProfileMenu.tsx:411 7632 msgid "Remove verification" 7633 msgstr "" 7634 ··· 7763 msgid "Report" 7764 msgstr "" 7765 7766 + #: src/view/com/profile/ProfileMenu.tsx:475 7767 + #: src/view/com/profile/ProfileMenu.tsx:478 7768 msgid "Report account" 7769 msgstr "" 7770 ··· 8074 msgid "Save changes" 8075 msgstr "" 8076 8077 + #: src/components/StarterPack/ShareDialog.tsx:142 8078 + #: src/components/StarterPack/ShareDialog.tsx:148 8079 msgid "Save image" 8080 msgstr "" 8081 ··· 8223 msgid "Search my posts" 8224 msgstr "" 8225 8226 + #: src/view/com/profile/ProfileMenu.tsx:291 8227 + #: src/view/com/profile/ProfileMenu.tsx:294 8228 msgid "Search posts" 8229 msgstr "" 8230 ··· 8350 msgid "Select content languages" 8351 msgstr "" 8352 8353 + #: src/components/live/GoLiveDialog.tsx:178 8354 msgid "Select duration" 8355 msgstr "" 8356 ··· 8384 msgid "Select language" 8385 msgstr "" 8386 8387 + #: src/view/com/composer/videos/SubtitleDialog.tsx:269 8388 msgid "Select language..." 8389 msgstr "" 8390 ··· 8642 msgid "Share a fun fact!" 8643 msgstr "" 8644 8645 + #: src/view/com/profile/ProfileMenu.tsx:562 8646 msgid "Share anyway" 8647 msgstr "" 8648 ··· 8654 #: src/components/dialogs/LinkWarning.tsx:96 8655 #: src/components/dialogs/LinkWarning.tsx:104 8656 #: src/components/StarterPack/ShareDialog.tsx:113 8657 + #: src/components/StarterPack/ShareDialog.tsx:122 8658 msgid "Share link" 8659 msgstr "" 8660 ··· 8667 msgid "Share post at:// URI" 8668 msgstr "" 8669 8670 + #: src/components/StarterPack/ShareDialog.tsx:127 8671 + #: src/components/StarterPack/ShareDialog.tsx:137 8672 msgid "Share QR code" 8673 msgstr "" 8674 ··· 8974 msgid "Someone reacted {0}" 8975 msgstr "" 8976 8977 + #: src/screens/Messages/components/ChatListItem.tsx:243 8978 msgid "Someone reacted {0} to {1}" 8979 msgstr "" 8980 ··· 9404 9405 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:186 9406 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:391 9407 + #: src/view/com/profile/ProfileMenu.tsx:538 9408 msgid "The account will be able to interact with you after unblocking." 9409 msgstr "" 9410 ··· 9442 msgid "The Discover feed now knows what you like" 9443 msgstr "" 9444 9445 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:332 9446 msgid "The experience is better in the app. Download Bluesky now and we'll pick back up where you left off." 9447 msgstr "" 9448 ··· 9458 msgid "The following labels were applied to your content." 9459 msgstr "" 9460 9461 + #: src/components/live/GoLiveDialog.tsx:160 9462 msgid "The following services are enabled for your account: {allowedServices}" 9463 msgstr "" 9464 ··· 9551 msgstr "" 9552 9553 #: src/screens/Search/Explore.tsx:999 9554 + #: src/view/com/posts/PostFeed.tsx:758 9555 msgid "There was an issue fetching posts. Tap here to try again." 9556 msgstr "" 9557 ··· 9702 msgid "This content is not viewable without a Bluesky account." 9703 msgstr "" 9704 9705 + #: src/screens/Messages/components/ChatListItem.tsx:365 9706 msgid "This conversation is with a deleted or a deactivated account. Press for options" 9707 msgstr "" 9708 ··· 9757 msgstr "" 9758 9759 #: src/components/live/EditLiveDialog.tsx:176 9760 + #: src/components/live/GoLiveDialog.tsx:153 9761 msgid "This is not a valid link" 9762 msgstr "" 9763 ··· 9817 msgid "This post's author has disabled quote posts." 9818 msgstr "" 9819 9820 + #: src/view/com/profile/ProfileMenu.tsx:559 9821 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in." 9822 msgstr "" 9823 ··· 10090 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:394 10091 #: src/screens/ProfileList/components/Header.tsx:171 10092 #: src/screens/ProfileList/components/Header.tsx:178 10093 + #: src/view/com/profile/ProfileMenu.tsx:550 10094 msgid "Unblock" 10095 msgstr "" 10096 ··· 10101 10102 #: src/components/dms/ConvoMenu.tsx:261 10103 #: src/components/dms/ConvoMenu.tsx:264 10104 + #: src/view/com/profile/ProfileMenu.tsx:455 10105 + #: src/view/com/profile/ProfileMenu.tsx:461 10106 msgid "Unblock account" 10107 msgstr "" 10108 10109 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:184 10110 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:389 10111 + #: src/view/com/profile/ProfileMenu.tsx:532 10112 msgid "Unblock Account?" 10113 msgstr "" 10114 ··· 10141 msgid "Unfollow {0}" 10142 msgstr "" 10143 10144 + #: src/view/com/profile/ProfileMenu.tsx:311 10145 + #: src/view/com/profile/ProfileMenu.tsx:321 10146 msgid "Unfollow account" 10147 msgstr "" 10148 ··· 10184 msgstr "" 10185 10186 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152 10187 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96 10188 msgctxt "video" 10189 msgid "Unmute" 10190 msgstr "" ··· 10201 10202 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:685 10203 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:691 10204 + #: src/view/com/profile/ProfileMenu.tsx:434 10205 + #: src/view/com/profile/ProfileMenu.tsx:440 10206 msgid "Unmute account" 10207 msgstr "" 10208 ··· 10220 msgid "Unmute thread" 10221 msgstr "" 10222 10223 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:317 10224 msgid "Unmute video" 10225 msgstr "" 10226 ··· 10339 msgid "Upload a text file to:" 10340 msgstr "" 10341 10342 + #: src/view/com/util/UserAvatar.tsx:469 10343 + #: src/view/com/util/UserAvatar.tsx:472 10344 #: src/view/com/util/UserBanner.tsx:159 10345 #: src/view/com/util/UserBanner.tsx:162 10346 msgid "Upload from Camera" 10347 msgstr "" 10348 10349 + #: src/view/com/util/UserAvatar.tsx:486 10350 #: src/view/com/util/UserBanner.tsx:176 10351 msgid "Upload from Files" 10352 msgstr "" 10353 10354 + #: src/view/com/util/UserAvatar.tsx:480 10355 + #: src/view/com/util/UserAvatar.tsx:484 10356 #: src/view/com/util/UserBanner.tsx:170 10357 #: src/view/com/util/UserBanner.tsx:174 10358 msgid "Upload from Library" ··· 10510 10511 #: src/components/verification/VerificationCreatePrompt.tsx:84 10512 #: src/components/verification/VerificationCreatePrompt.tsx:86 10513 + #: src/view/com/profile/ProfileMenu.tsx:418 10514 + #: src/view/com/profile/ProfileMenu.tsx:421 10515 msgid "Verify account" 10516 msgstr "" 10517 ··· 10626 msgid "Video not found." 10627 msgstr "" 10628 10629 + #: src/view/com/composer/videos/SubtitleDialog.tsx:106 10630 msgid "Video settings" 10631 msgstr "" 10632 ··· 11120 msgstr "" 11121 11122 #: src/components/live/EditLiveDialog.tsx:152 11123 + #: src/components/live/GoLiveDialog.tsx:134 11124 msgid "www.mylivestream.tv" 11125 msgstr "" 11126 ··· 11464 msgid "You reacted {0}" 11465 msgstr "" 11466 11467 + #: src/screens/Messages/components/ChatListItem.tsx:220 11468 msgid "You reacted {0} to {1}" 11469 msgstr "" 11470 ··· 11494 msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." 11495 msgstr "" 11496 11497 + #: src/screens/Messages/components/ChatListItem.tsx:156 11498 msgid "You: {0}" 11499 msgstr "" 11500 11501 + #: src/screens/Messages/components/ChatListItem.tsx:185 11502 msgid "You: {defaultEmbeddedContentMessage}" 11503 msgstr "" 11504 11505 + #: src/screens/Messages/components/ChatListItem.tsx:178 11506 msgid "You: {short}" 11507 msgstr "" 11508 ··· 11514 msgid "You'll follow the suggested users once you finish creating your account!" 11515 msgstr "" 11516 11517 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:244 11518 msgid "You'll follow these people and {0} others" 11519 msgstr "" 11520 11521 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:242 11522 msgid "You'll follow these people right away" 11523 msgstr "" 11524 ··· 11530 msgid "You'll start receiving notifications for {0}!" 11531 msgstr "" 11532 11533 + #: src/screens/StarterPack/StarterPackLandingScreen.tsx:282 11534 msgid "You'll stay updated with these feeds" 11535 msgstr "" 11536
+2 -2
src/logger/index.ts
··· 11 type Transport, 12 } from '#/logger/types' 13 import {enabledLogLevels} from '#/logger/util' 14 - import {isNative} from '#/platform/detection' 15 import {ENV} from '#/env' 16 import {bitdriftTransport} from './transports/bitdrift' 17 import {sentryTransport} from './transports/sentry' ··· 21 const TRANSPORTS: Transport[] = (function configureTransports() { 22 switch (ENV) { 23 case 'production': { 24 - return [sentryTransport, isNative && bitdriftTransport].filter( 25 Boolean, 26 ) as Transport[] 27 }
··· 11 type Transport, 12 } from '#/logger/types' 13 import {enabledLogLevels} from '#/logger/util' 14 + import {IS_NATIVE} from '#/env' 15 import {ENV} from '#/env' 16 import {bitdriftTransport} from './transports/bitdrift' 17 import {sentryTransport} from './transports/sentry' ··· 21 const TRANSPORTS: Transport[] = (function configureTransports() { 22 switch (ENV) { 23 case 'production': { 24 + return [sentryTransport, IS_NATIVE && bitdriftTransport].filter( 25 Boolean, 26 ) as Transport[] 27 }
+2 -2
src/logger/transports/console.ts
··· 2 3 import {LogLevel, type Transport} from '#/logger/types' 4 import {prepareMetadata} from '#/logger/util' 5 - import {isWeb} from '#/platform/detection' 6 7 /** 8 * Used in dev mode to nicely log to the console ··· 33 msg += ` ${message.toString()}` 34 } 35 36 - if (isWeb) { 37 if (hasMetadata) { 38 console.groupCollapsed(msg) 39 console.log(metadata)
··· 2 3 import {LogLevel, type Transport} from '#/logger/types' 4 import {prepareMetadata} from '#/logger/util' 5 + import {IS_WEB} from '#/env' 6 7 /** 8 * Used in dev mode to nicely log to the console ··· 33 msg += ` ${message.toString()}` 34 } 35 36 + if (IS_WEB) { 37 if (hasMetadata) { 38 console.groupCollapsed(msg) 39 console.log(metadata)
-12
src/platform/detection.ts
··· 1 - import {Platform} from 'react-native' 2 - 3 - export const isIOS = Platform.OS === 'ios' 4 - export const isAndroid = Platform.OS === 'android' 5 - export const isNative = isIOS || isAndroid 6 - export const isWeb = !isNative 7 - export const isMobileWebMediaQuery = 'only screen and (max-width: 1300px)' 8 - export const isMobileWeb = 9 - isWeb && 10 - // @ts-ignore we know window exists -prf 11 - global.window.matchMedia(isMobileWebMediaQuery)?.matches 12 - export const isIPhoneWeb = isWeb && /iPhone/.test(navigator.userAgent)
···
-26
src/platform/urls.tsx
··· 1 - import {Linking} from 'react-native' 2 - 3 - import {isNative, isWeb} from './detection' 4 - 5 - export async function getInitialURL(): Promise<string | undefined> { 6 - if (isNative) { 7 - const url = await Linking.getInitialURL() 8 - if (url) { 9 - return url 10 - } 11 - return undefined 12 - } else { 13 - // @ts-ignore window exists -prf 14 - if (window.location.pathname !== '/') { 15 - return window.location.pathname 16 - } 17 - return undefined 18 - } 19 - } 20 - 21 - export function clearHash() { 22 - if (isWeb) { 23 - // @ts-ignore window exists -prf 24 - window.location.hash = '' 25 - } 26 - }
···
+2 -2
src/screens/Bookmarks/index.tsx
··· 21 type NativeStackScreenProps, 22 } from '#/lib/routes/types' 23 import {logger} from '#/logger' 24 - import {isIOS} from '#/platform/detection' 25 import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' 26 import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery' 27 import {useSetMinimalShellMode} from '#/state/shell' ··· 38 import * as Skele from '#/components/Skeleton' 39 import * as toast from '#/components/Toast' 40 import {Text} from '#/components/Typography' 41 42 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'> 43 ··· 193 } 194 initialNumToRender={initialNumToRender} 195 windowSize={9} 196 - maxToRenderPerBatch={isIOS ? 5 : 1} 197 updateCellsBatchingPeriod={40} 198 sideBorders={false} 199 />
··· 21 type NativeStackScreenProps, 22 } from '#/lib/routes/types' 23 import {logger} from '#/logger' 24 import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' 25 import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery' 26 import {useSetMinimalShellMode} from '#/state/shell' ··· 37 import * as Skele from '#/components/Skeleton' 38 import * as toast from '#/components/Toast' 39 import {Text} from '#/components/Typography' 40 + import {IS_IOS} from '#/env' 41 42 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'> 43 ··· 193 } 194 initialNumToRender={initialNumToRender} 195 windowSize={9} 196 + maxToRenderPerBatch={IS_IOS ? 5 : 1} 197 updateCellsBatchingPeriod={40} 198 sideBorders={false} 199 />
+4 -4
src/screens/Deactivated.tsx
··· 7 8 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 9 import {logger} from '#/logger' 10 - import {isWeb} from '#/platform/detection' 11 import { 12 type SessionAccount, 13 useAgent, ··· 25 import * as Layout from '#/components/Layout' 26 import {Loader} from '#/components/Loader' 27 import {Text} from '#/components/Typography' 28 29 const COL_WIDTH = 400 30 ··· 56 }, [setShowLoggedOut]) 57 58 const onPressLogout = React.useCallback(() => { 59 - if (isWeb) { 60 // We're switching accounts, which remounts the entire app. 61 // On mobile, this gets us Home, but on the web we also need reset the URL. 62 // We can't change the URL via a navigate() call because the navigator ··· 102 contentContainerStyle={[ 103 a.px_2xl, 104 { 105 - paddingTop: isWeb ? 64 : insets.top + 16, 106 - paddingBottom: isWeb ? 64 : insets.bottom, 107 }, 108 ]}> 109 <View
··· 7 8 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 9 import {logger} from '#/logger' 10 import { 11 type SessionAccount, 12 useAgent, ··· 24 import * as Layout from '#/components/Layout' 25 import {Loader} from '#/components/Loader' 26 import {Text} from '#/components/Typography' 27 + import {IS_WEB} from '#/env' 28 29 const COL_WIDTH = 400 30 ··· 56 }, [setShowLoggedOut]) 57 58 const onPressLogout = React.useCallback(() => { 59 + if (IS_WEB) { 60 // We're switching accounts, which remounts the entire app. 61 // On mobile, this gets us Home, but on the web we also need reset the URL. 62 // We can't change the URL via a navigate() call because the navigator ··· 102 contentContainerStyle={[ 103 a.px_2xl, 104 { 105 + paddingTop: IS_WEB ? 64 : insets.top + 16, 106 + paddingBottom: IS_WEB ? 64 : insets.bottom, 107 }, 108 ]}> 109 <View
+2 -2
src/screens/FindContactsFlowScreen.tsx
··· 9 type AllNavigatorParams, 10 type NativeStackScreenProps, 11 } from '#/lib/routes/types' 12 - import {isNative} from '#/platform/detection' 13 import {useSetMinimalShellMode} from '#/state/shell' 14 import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' 15 import {FindContactsFlow} from '#/components/contacts/FindContactsFlow' 16 import {useFindContactsFlowState} from '#/components/contacts/state' 17 import * as Layout from '#/components/Layout' 18 import {ScreenTransition} from '#/components/ScreenTransition' 19 20 type Props = NativeStackScreenProps<AllNavigatorParams, 'FindContactsFlow'> 21 export function FindContactsFlowScreen({navigation}: Props) { ··· 48 49 return ( 50 <Layout.Screen> 51 - {isNative ? ( 52 <LayoutAnimationConfig skipEntering skipExiting> 53 <ScreenTransition key={state.step} direction={transitionDirection}> 54 <FindContactsFlow
··· 9 type AllNavigatorParams, 10 type NativeStackScreenProps, 11 } from '#/lib/routes/types' 12 import {useSetMinimalShellMode} from '#/state/shell' 13 import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' 14 import {FindContactsFlow} from '#/components/contacts/FindContactsFlow' 15 import {useFindContactsFlowState} from '#/components/contacts/state' 16 import * as Layout from '#/components/Layout' 17 import {ScreenTransition} from '#/components/ScreenTransition' 18 + import {IS_NATIVE} from '#/env' 19 20 type Props = NativeStackScreenProps<AllNavigatorParams, 'FindContactsFlow'> 21 export function FindContactsFlowScreen({navigation}: Props) { ··· 48 49 return ( 50 <Layout.Screen> 51 + {IS_NATIVE ? ( 52 <LayoutAnimationConfig skipEntering skipExiting> 53 <ScreenTransition key={state.step} direction={transitionDirection}> 54 <FindContactsFlow
+2 -2
src/screens/Login/LoginForm.tsx
··· 18 import {cleanError} from '#/lib/strings/errors' 19 import {createFullHandle} from '#/lib/strings/handles' 20 import {logger} from '#/logger' 21 - import {isIOS} from '#/platform/detection' 22 import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 23 import {useSessionApi} from '#/state/session' 24 import {useLoggedOutViewControls} from '#/state/shell/logged-out' ··· 32 import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket' 33 import {Loader} from '#/components/Loader' 34 import {Text} from '#/components/Typography' 35 import {FormContainer} from './FormContainer' 36 37 type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema ··· 217 inputRef={identifierRef} 218 label={_(msg`Username or email address`)} 219 autoCapitalize="none" 220 - autoFocus={!isIOS} 221 autoCorrect={false} 222 autoComplete="username" 223 returnKeyType="next"
··· 18 import {cleanError} from '#/lib/strings/errors' 19 import {createFullHandle} from '#/lib/strings/handles' 20 import {logger} from '#/logger' 21 import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 22 import {useSessionApi} from '#/state/session' 23 import {useLoggedOutViewControls} from '#/state/shell/logged-out' ··· 31 import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket' 32 import {Loader} from '#/components/Loader' 33 import {Text} from '#/components/Typography' 34 + import {IS_IOS} from '#/env' 35 import {FormContainer} from './FormContainer' 36 37 type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema ··· 217 inputRef={identifierRef} 218 label={_(msg`Username or email address`)} 219 autoCapitalize="none" 220 + autoFocus={!IS_IOS} 221 autoCorrect={false} 222 autoComplete="username" 223 returnKeyType="next"
+3 -3
src/screens/Messages/ChatList.tsx
··· 13 import {type MessagesTabNavigatorParams} from '#/lib/routes/types' 14 import {cleanError} from '#/lib/strings/errors' 15 import {logger} from '#/logger' 16 - import {isNative} from '#/platform/detection' 17 import {listenSoftReset} from '#/state/events' 18 import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const' 19 import {useMessagesEventBus} from '#/state/messages/events' ··· 39 import {Link} from '#/components/Link' 40 import {ListFooter} from '#/components/Lists' 41 import {Text} from '#/components/Typography' 42 import {ChatListItem} from './components/ChatListItem' 43 import {InboxPreview} from './components/InboxPreview' 44 ··· 223 224 const onSoftReset = useCallback(async () => { 225 scrollElRef.current?.scrollToOffset({ 226 - animated: isNative, 227 offset: 0, 228 }) 229 try { ··· 349 hasNextPage={hasNextPage} 350 /> 351 } 352 - onEndReachedThreshold={isNative ? 1.5 : 0} 353 initialNumToRender={initialNumToRender} 354 windowSize={11} 355 desktopFixedHeight
··· 13 import {type MessagesTabNavigatorParams} from '#/lib/routes/types' 14 import {cleanError} from '#/lib/strings/errors' 15 import {logger} from '#/logger' 16 import {listenSoftReset} from '#/state/events' 17 import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const' 18 import {useMessagesEventBus} from '#/state/messages/events' ··· 38 import {Link} from '#/components/Link' 39 import {ListFooter} from '#/components/Lists' 40 import {Text} from '#/components/Typography' 41 + import {IS_NATIVE} from '#/env' 42 import {ChatListItem} from './components/ChatListItem' 43 import {InboxPreview} from './components/InboxPreview' 44 ··· 223 224 const onSoftReset = useCallback(async () => { 225 scrollElRef.current?.scrollToOffset({ 226 + animated: IS_NATIVE, 227 offset: 0, 228 }) 229 try { ··· 349 hasNextPage={hasNextPage} 350 /> 351 } 352 + onEndReachedThreshold={IS_NATIVE ? 1.5 : 0} 353 initialNumToRender={initialNumToRender} 354 windowSize={11} 355 desktopFixedHeight
+2 -2
src/screens/Messages/Conversation.tsx
··· 21 type CommonNavigatorParams, 22 type NavigationProp, 23 } from '#/lib/routes/types' 24 - import {isWeb} from '#/platform/detection' 25 import {type Shadow, useMaybeProfileShadow} from '#/state/cache/profile-shadow' 26 import {useEmail} from '#/state/email-verification' 27 import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' ··· 43 import {Error} from '#/components/Error' 44 import * as Layout from '#/components/Layout' 45 import {Loader} from '#/components/Loader' 46 47 type Props = NativeStackScreenProps< 48 CommonNavigatorParams, ··· 74 useCallback(() => { 75 setCurrentConvoId(convoId) 76 77 - if (isWeb && !gtMobile) { 78 setMinimalShellMode(true) 79 } else { 80 setMinimalShellMode(false)
··· 21 type CommonNavigatorParams, 22 type NavigationProp, 23 } from '#/lib/routes/types' 24 import {type Shadow, useMaybeProfileShadow} from '#/state/cache/profile-shadow' 25 import {useEmail} from '#/state/email-verification' 26 import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' ··· 42 import {Error} from '#/components/Error' 43 import * as Layout from '#/components/Layout' 44 import {Loader} from '#/components/Loader' 45 + import {IS_WEB} from '#/env' 46 47 type Props = NativeStackScreenProps< 48 CommonNavigatorParams, ··· 74 useCallback(() => { 75 setCurrentConvoId(convoId) 76 77 + if (IS_WEB && !gtMobile) { 78 setMinimalShellMode(true) 79 } else { 80 setMinimalShellMode(false)
+2 -2
src/screens/Messages/Inbox.tsx
··· 21 } from '#/lib/routes/types' 22 import {cleanError} from '#/lib/strings/errors' 23 import {logger} from '#/logger' 24 - import {isNative} from '#/platform/detection' 25 import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const' 26 import {useMessagesEventBus} from '#/state/messages/events' 27 import {useLeftConvos} from '#/state/queries/messages/leave-conversation' ··· 44 import * as Layout from '#/components/Layout' 45 import {ListFooter} from '#/components/Lists' 46 import {Text} from '#/components/Typography' 47 import {RequestListItem} from './components/RequestListItem' 48 49 type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesInbox'> ··· 288 hasNextPage={hasNextPage} 289 /> 290 } 291 - onEndReachedThreshold={isNative ? 1.5 : 0} 292 initialNumToRender={initialNumToRender} 293 windowSize={11} 294 desktopFixedHeight
··· 21 } from '#/lib/routes/types' 22 import {cleanError} from '#/lib/strings/errors' 23 import {logger} from '#/logger' 24 import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const' 25 import {useMessagesEventBus} from '#/state/messages/events' 26 import {useLeftConvos} from '#/state/queries/messages/leave-conversation' ··· 43 import * as Layout from '#/components/Layout' 44 import {ListFooter} from '#/components/Lists' 45 import {Text} from '#/components/Typography' 46 + import {IS_NATIVE} from '#/env' 47 import {RequestListItem} from './components/RequestListItem' 48 49 type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesInbox'> ··· 288 hasNextPage={hasNextPage} 289 /> 290 } 291 + onEndReachedThreshold={IS_NATIVE ? 1.5 : 0} 292 initialNumToRender={initialNumToRender} 293 windowSize={11} 294 desktopFixedHeight
+2 -2
src/screens/Messages/Settings.tsx
··· 5 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 6 7 import {type CommonNavigatorParams} from '#/lib/routes/types' 8 - import {isNative} from '#/platform/detection' 9 import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration' 10 import {useProfileQuery} from '#/state/queries/profile' 11 import {useSession} from '#/state/session' ··· 16 import * as Toggle from '#/components/forms/Toggle' 17 import * as Layout from '#/components/Layout' 18 import {Text} from '#/components/Typography' 19 import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 20 21 type AllowIncoming = 'all' | 'none' | 'following' ··· 118 you choose. 119 </Trans> 120 </Admonition> 121 - {isNative && ( 122 <> 123 <Divider style={a.my_md} /> 124 <Text style={[a.text_lg, a.font_semi_bold]}>
··· 5 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 6 7 import {type CommonNavigatorParams} from '#/lib/routes/types' 8 import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration' 9 import {useProfileQuery} from '#/state/queries/profile' 10 import {useSession} from '#/state/session' ··· 15 import * as Toggle from '#/components/forms/Toggle' 16 import * as Layout from '#/components/Layout' 17 import {Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 20 21 type AllowIncoming = 'all' | 'none' | 'following' ··· 118 you choose. 119 </Trans> 120 </Admonition> 121 + {IS_NATIVE && ( 122 <> 123 <Divider style={a.my_md} /> 124 <Text style={[a.text_lg, a.font_semi_bold]}>
+6 -7
src/screens/Messages/components/ChatListItem.tsx
··· 20 toBskyAppUrl, 21 toShortUrl, 22 } from '#/lib/strings/url-helpers' 23 - import {isNative} from '#/platform/detection' 24 import {useProfileShadow} from '#/state/cache/profile-shadow' 25 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 26 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 47 import {Text} from '#/components/Typography' 48 import {useSimpleVerificationState} from '#/components/verification' 49 import {VerificationCheck} from '#/components/verification/VerificationCheck' 50 import type * as bsky from '#/types/bsky' 51 52 export const ChatListItemPortal = createPortalGroup() ··· 142 143 const {lastMessage, lastMessageSentAt, latestReportableMessage} = 144 useMemo(() => { 145 - // eslint-disable-next-line @typescript-eslint/no-shadow 146 let lastMessage = _(msg`No messages yet`) 147 - // eslint-disable-next-line @typescript-eslint/no-shadow 148 let lastMessageSentAt: string | null = null 149 - // eslint-disable-next-line @typescript-eslint/no-shadow 150 let latestReportableMessage: ChatBskyConvoDefs.MessageView | undefined 151 152 if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { ··· 371 ) 372 } 373 accessibilityActions={ 374 - isNative 375 ? [ 376 { 377 name: 'magicTap', ··· 385 : undefined 386 } 387 onPress={onPress} 388 - onLongPress={isNative ? onLongPress : undefined} 389 onAccessibilityAction={onLongPress}> 390 {({hovered, pressed, focused}) => ( 391 <View ··· 524 control={menuControl} 525 currentScreen="list" 526 showMarkAsRead={convo.unreadCount > 0} 527 - hideTrigger={isNative} 528 blockInfo={blockInfo} 529 style={[ 530 a.absolute,
··· 20 toBskyAppUrl, 21 toShortUrl, 22 } from '#/lib/strings/url-helpers' 23 import {useProfileShadow} from '#/state/cache/profile-shadow' 24 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 25 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 46 import {Text} from '#/components/Typography' 47 import {useSimpleVerificationState} from '#/components/verification' 48 import {VerificationCheck} from '#/components/verification/VerificationCheck' 49 + import {IS_NATIVE} from '#/env' 50 import type * as bsky from '#/types/bsky' 51 52 export const ChatListItemPortal = createPortalGroup() ··· 142 143 const {lastMessage, lastMessageSentAt, latestReportableMessage} = 144 useMemo(() => { 145 let lastMessage = _(msg`No messages yet`) 146 + 147 let lastMessageSentAt: string | null = null 148 + 149 let latestReportableMessage: ChatBskyConvoDefs.MessageView | undefined 150 151 if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { ··· 370 ) 371 } 372 accessibilityActions={ 373 + IS_NATIVE 374 ? [ 375 { 376 name: 'magicTap', ··· 384 : undefined 385 } 386 onPress={onPress} 387 + onLongPress={IS_NATIVE ? onLongPress : undefined} 388 onAccessibilityAction={onLongPress}> 389 {({hovered, pressed, focused}) => ( 390 <View ··· 523 control={menuControl} 524 currentScreen="list" 525 showMarkAsRead={convo.unreadCount > 0} 526 + hideTrigger={IS_NATIVE} 527 blockInfo={blockInfo} 528 style={[ 529 a.absolute,
+5 -5
src/screens/Messages/components/MessageInput.tsx
··· 18 19 import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' 20 import {useHaptics} from '#/lib/haptics' 21 - import {isIOS, isWeb} from '#/platform/detection' 22 import {useEmail} from '#/state/email-verification' 23 import { 24 useMessageDraft, ··· 30 import {android, atoms as a, useTheme} from '#/alf' 31 import {useSharedInputStyles} from '#/components/forms/TextField' 32 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 33 import {useExtractEmbedFromFacets} from './MessageInputEmbed' 34 35 const AnimatedTextInput = Animated.createAnimatedComponent(TextInput) ··· 87 playHaptic() 88 setEmbed(undefined) 89 setMessage('') 90 - if (isIOS) { 91 setShouldEnforceClear(true) 92 } 93 - if (isWeb) { 94 // Pressing the send button causes the text input to lose focus, so we need to 95 // re-focus it after sending 96 setTimeout(() => { ··· 163 // next change and double make sure the input is cleared. It should *always* send an onChange event after 164 // clearing via setMessage('') that happens in onSubmit() 165 // -sfn 166 - if (isIOS && shouldEnforceClear) { 167 setShouldEnforceClear(false) 168 setMessage('') 169 return ··· 178 a.px_sm, 179 t.atoms.text, 180 android({paddingTop: 0}), 181 - {paddingBottom: isIOS ? 5 : 0}, 182 animatedStyle, 183 ]} 184 keyboardAppearance={t.scheme}
··· 18 19 import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' 20 import {useHaptics} from '#/lib/haptics' 21 import {useEmail} from '#/state/email-verification' 22 import { 23 useMessageDraft, ··· 29 import {android, atoms as a, useTheme} from '#/alf' 30 import {useSharedInputStyles} from '#/components/forms/TextField' 31 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 32 + import {IS_IOS, IS_WEB} from '#/env' 33 import {useExtractEmbedFromFacets} from './MessageInputEmbed' 34 35 const AnimatedTextInput = Animated.createAnimatedComponent(TextInput) ··· 87 playHaptic() 88 setEmbed(undefined) 89 setMessage('') 90 + if (IS_IOS) { 91 setShouldEnforceClear(true) 92 } 93 + if (IS_WEB) { 94 // Pressing the send button causes the text input to lose focus, so we need to 95 // re-focus it after sending 96 setTimeout(() => { ··· 163 // next change and double make sure the input is cleared. It should *always* send an onChange event after 164 // clearing via setMessage('') that happens in onSubmit() 165 // -sfn 166 + if (IS_IOS && shouldEnforceClear) { 167 setShouldEnforceClear(false) 168 setMessage('') 169 return ··· 178 a.px_sm, 179 t.atoms.text, 180 android({paddingTop: 0}), 181 + {paddingBottom: IS_IOS ? 5 : 0}, 182 animatedStyle, 183 ]} 184 keyboardAppearance={t.scheme}
+3 -3
src/screens/Messages/components/MessageInput.web.tsx
··· 6 import TextareaAutosize from 'react-textarea-autosize' 7 import {countGraphemes} from 'unicode-segmenter/grapheme' 8 9 - import {isSafari, isTouchDevice} from '#/lib/browser' 10 import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' 11 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 12 import { ··· 25 import {useSharedInputStyles} from '#/components/forms/TextField' 26 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' 27 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 28 import {useExtractEmbedFromFacets} from './MessageInputEmbed' 29 30 export function MessageInput({ ··· 88 // far too long of a delay, and a subsequent enter press would often just end up doing nothing. A shorter time 89 // frame was also not great, since it was too short to be reliable (i.e. an older system might have a larger 90 // time gap between the two events firing. 91 - if (isSafari && e.key === 'Enter' && e.keyCode === 229) { 92 return 93 } 94 ··· 231 onChange={onChange} 232 // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message 233 // in these cases. 234 - onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown} 235 /> 236 <Pressable 237 accessibilityRole="button"
··· 6 import TextareaAutosize from 'react-textarea-autosize' 7 import {countGraphemes} from 'unicode-segmenter/grapheme' 8 9 import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' 10 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 11 import { ··· 24 import {useSharedInputStyles} from '#/components/forms/TextField' 25 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' 26 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 27 + import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env' 28 import {useExtractEmbedFromFacets} from './MessageInputEmbed' 29 30 export function MessageInput({ ··· 88 // far too long of a delay, and a subsequent enter press would often just end up doing nothing. A shorter time 89 // frame was also not great, since it was too short to be reliable (i.e. an older system might have a larger 90 // time gap between the two events firing. 91 + if (IS_WEB_SAFARI && e.key === 'Enter' && e.keyCode === 229) { 92 return 93 } 94 ··· 231 onChange={onChange} 232 // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message 233 // in these cases. 234 + onKeyDown={IS_WEB_TOUCH_DEVICE && isMobile ? undefined : onKeyDown} 235 /> 236 <Pressable 237 accessibilityRole="button"
+7 -7
src/screens/Messages/components/MessagesList.tsx
··· 24 isBskyPostUrl, 25 } from '#/lib/strings/url-helpers' 26 import {logger} from '#/logger' 27 - import {isNative} from '#/platform/detection' 28 - import {isWeb} from '#/platform/detection' 29 import { 30 type ActiveConvoStates, 31 isConvoActive, ··· 52 import {NewMessagesPill} from '#/components/dms/NewMessagesPill' 53 import {Loader} from '#/components/Loader' 54 import {Text} from '#/components/Typography' 55 import {ChatStatusInfo} from './ChatStatusInfo' 56 import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed' 57 ··· 159 (_: number, height: number) => { 160 // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the 161 // previous off whenever we add new content to the previous offset whenever we add new content to the list. 162 - if (isWeb && isAtTop.get() && hasScrolled) { 163 flatListRef.current?.scrollToOffset({ 164 offset: height - prevContentHeight.current, 165 animated: false, ··· 399 (e: LayoutChangeEvent) => { 400 layoutHeight.set(e.nativeEvent.layout.height) 401 402 - if (isWeb || !keyboardIsOpening.get()) { 403 flatListRef.current?.scrollToEnd({ 404 animated: !layoutScrollWithoutAnimation.get(), 405 }) ··· 438 disableVirtualization={true} 439 style={animatedListStyle} 440 // The extra two items account for the header and the footer components 441 - initialNumToRender={isNative ? 32 : 62} 442 - maxToRenderPerBatch={isWeb ? 32 : 62} 443 keyboardDismissMode="on-drag" 444 keyboardShouldPersistTaps="handled" 445 maintainVisibleContentPosition={{ ··· 477 )} 478 </Animated.View> 479 480 - {isWeb && ( 481 <EmojiPicker 482 pinToTop 483 state={emojiPickerState}
··· 24 isBskyPostUrl, 25 } from '#/lib/strings/url-helpers' 26 import {logger} from '#/logger' 27 import { 28 type ActiveConvoStates, 29 isConvoActive, ··· 50 import {NewMessagesPill} from '#/components/dms/NewMessagesPill' 51 import {Loader} from '#/components/Loader' 52 import {Text} from '#/components/Typography' 53 + import {IS_NATIVE} from '#/env' 54 + import {IS_WEB} from '#/env' 55 import {ChatStatusInfo} from './ChatStatusInfo' 56 import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed' 57 ··· 159 (_: number, height: number) => { 160 // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the 161 // previous off whenever we add new content to the previous offset whenever we add new content to the list. 162 + if (IS_WEB && isAtTop.get() && hasScrolled) { 163 flatListRef.current?.scrollToOffset({ 164 offset: height - prevContentHeight.current, 165 animated: false, ··· 399 (e: LayoutChangeEvent) => { 400 layoutHeight.set(e.nativeEvent.layout.height) 401 402 + if (IS_WEB || !keyboardIsOpening.get()) { 403 flatListRef.current?.scrollToEnd({ 404 animated: !layoutScrollWithoutAnimation.get(), 405 }) ··· 438 disableVirtualization={true} 439 style={animatedListStyle} 440 // The extra two items account for the header and the footer components 441 + initialNumToRender={IS_NATIVE ? 32 : 62} 442 + maxToRenderPerBatch={IS_WEB ? 32 : 62} 443 keyboardDismissMode="on-drag" 444 keyboardShouldPersistTaps="handled" 445 maintainVisibleContentPosition={{ ··· 477 )} 478 </Animated.View> 479 480 + {IS_WEB && ( 481 <EmojiPicker 482 pinToTop 483 state={emojiPickerState}
+2 -2
src/screens/Moderation/index.tsx
··· 11 type NativeStackScreenProps, 12 } from '#/lib/routes/types' 13 import {logger} from '#/logger' 14 - import {isIOS} from '#/platform/detection' 15 import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 16 import { 17 useMyLabelersQuery, ··· 45 import {GlobalLabelPreference} from '#/components/moderation/LabelPreference' 46 import {Text} from '#/components/Typography' 47 import {useAgeAssurance} from '#/ageAssurance' 48 49 function ErrorState({error}: {error: string}) { 50 const t = useTheme() ··· 182 (optimisticAdultContent && optimisticAdultContent.enabled) || 183 (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled) 184 ) 185 - const adultContentUIDisabledOnIOS = isIOS && !adultContentEnabled 186 let adultContentUIDisabled = adultContentUIDisabledOnIOS 187 188 if (aa.flags.adultContentDisabled) {
··· 11 type NativeStackScreenProps, 12 } from '#/lib/routes/types' 13 import {logger} from '#/logger' 14 import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 15 import { 16 useMyLabelersQuery, ··· 44 import {GlobalLabelPreference} from '#/components/moderation/LabelPreference' 45 import {Text} from '#/components/Typography' 46 import {useAgeAssurance} from '#/ageAssurance' 47 + import {IS_IOS} from '#/env' 48 49 function ErrorState({error}: {error: string}) { 50 const t = useTheme() ··· 182 (optimisticAdultContent && optimisticAdultContent.enabled) || 183 (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled) 184 ) 185 + const adultContentUIDisabledOnIOS = IS_IOS && !adultContentEnabled 186 let adultContentUIDisabled = adultContentUIDisabledOnIOS 187 188 if (aa.flags.adultContentDisabled) {
+5 -5
src/screens/Onboarding/Layout.tsx
··· 4 import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 - import {isAndroid, isWeb} from '#/platform/detection' 8 import {useOnboardingDispatch} from '#/state/shell' 9 import {useOnboardingInternalState} from '#/screens/Onboarding/state' 10 import { ··· 21 import {HEADER_SLOT_SIZE} from '#/components/Layout' 22 import {createPortalGroup} from '#/components/Portal' 23 import {P, Text} from '#/components/Typography' 24 import {IS_INTERNAL} from '#/env' 25 26 const ONBOARDING_COL_WIDTH = 420 ··· 58 aria-label={dialogLabel} 59 accessibilityLabel={dialogLabel} 60 accessibilityHint={_(msg`Customizes your Bluesky experience`)} 61 - style={[isWeb ? a.fixed : a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}> 62 {!gtMobile ? ( 63 <View 64 style={[ ··· 151 paddingTop: gtMobile ? 40 : headerHeight, 152 paddingBottom: footerHeight, 153 }} 154 - showsVerticalScrollIndicator={!isAndroid} 155 scrollIndicatorInsets={{bottom: footerHeight - insets.bottom}} 156 // @ts-expect-error web only --prf 157 dataSet={{'stable-gutters': 1}} ··· 167 <View 168 onLayout={evt => setFooterHeight(evt.nativeEvent.layout.height)} 169 style={[ 170 - isWeb ? a.fixed : a.absolute, 171 {bottom: 0, left: 0, right: 0}, 172 t.atoms.bg, 173 t.atoms.border_contrast_low, 174 a.border_t, 175 a.align_center, 176 gtMobile ? a.px_5xl : a.px_xl, 177 - isWeb 178 ? a.py_2xl 179 : { 180 paddingTop: tokens.space.md,
··· 4 import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 import {useOnboardingDispatch} from '#/state/shell' 8 import {useOnboardingInternalState} from '#/screens/Onboarding/state' 9 import { ··· 20 import {HEADER_SLOT_SIZE} from '#/components/Layout' 21 import {createPortalGroup} from '#/components/Portal' 22 import {P, Text} from '#/components/Typography' 23 + import {IS_ANDROID, IS_WEB} from '#/env' 24 import {IS_INTERNAL} from '#/env' 25 26 const ONBOARDING_COL_WIDTH = 420 ··· 58 aria-label={dialogLabel} 59 accessibilityLabel={dialogLabel} 60 accessibilityHint={_(msg`Customizes your Bluesky experience`)} 61 + style={[IS_WEB ? a.fixed : a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}> 62 {!gtMobile ? ( 63 <View 64 style={[ ··· 151 paddingTop: gtMobile ? 40 : headerHeight, 152 paddingBottom: footerHeight, 153 }} 154 + showsVerticalScrollIndicator={!IS_ANDROID} 155 scrollIndicatorInsets={{bottom: footerHeight - insets.bottom}} 156 // @ts-expect-error web only --prf 157 dataSet={{'stable-gutters': 1}} ··· 167 <View 168 onLayout={evt => setFooterHeight(evt.nativeEvent.layout.height)} 169 style={[ 170 + IS_WEB ? a.fixed : a.absolute, 171 {bottom: 0, left: 0, right: 0}, 172 t.atoms.bg, 173 t.atoms.border_contrast_low, 174 a.border_t, 175 a.align_center, 176 gtMobile ? a.px_5xl : a.px_xl, 177 + IS_WEB 178 ? a.py_2xl 179 : { 180 paddingTop: tokens.space.md,
+2 -2
src/screens/Onboarding/StepFinished/index.tsx
··· 22 import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' 23 import {logEvent, useGate} from '#/lib/statsig/statsig' 24 import {logger} from '#/logger' 25 - import {isWeb} from '#/platform/detection' 26 import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 27 import {getAllListMembers} from '#/state/queries/list-members' 28 import {preferencesQueryKey} from '#/state/queries/preferences' ··· 47 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 48 import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow' 49 import {Loader} from '#/components/Loader' 50 import * as bsky from '#/types/bsky' 51 import {ValuePropositionPager} from './ValuePropositionPager' 52 ··· 305 306 <OnboardingControls.Portal> 307 <View style={gtMobile && [a.gap_md, a.flex_row]}> 308 - {gtMobile && (isWeb ? subStep !== 2 : true) && ( 309 <Button 310 disabled={saving} 311 color="secondary"
··· 22 import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' 23 import {logEvent, useGate} from '#/lib/statsig/statsig' 24 import {logger} from '#/logger' 25 import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 26 import {getAllListMembers} from '#/state/queries/list-members' 27 import {preferencesQueryKey} from '#/state/queries/preferences' ··· 46 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 47 import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow' 48 import {Loader} from '#/components/Loader' 49 + import {IS_WEB} from '#/env' 50 import * as bsky from '#/types/bsky' 51 import {ValuePropositionPager} from './ValuePropositionPager' 52 ··· 305 306 <OnboardingControls.Portal> 307 <View style={gtMobile && [a.gap_md, a.flex_row]}> 308 + {gtMobile && (IS_WEB ? subStep !== 2 : true) && ( 309 <Button 310 disabled={saving} 311 color="secondary"
+3 -3
src/screens/Onboarding/StepProfile/index.tsx
··· 17 import {logEvent, useGate} from '#/lib/statsig/statsig' 18 import {isCancelledError} from '#/lib/strings/errors' 19 import {logger} from '#/logger' 20 - import {isNative, isWeb} from '#/platform/detection' 21 import { 22 OnboardingControls, 23 OnboardingDescriptionText, ··· 38 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' 39 import {CircleInfo_Stroke2_Corner0_Rounded} from '#/components/icons/CircleInfo' 40 import {Text} from '#/components/Typography' 41 import {type AvatarColor, avatarColors, type Emoji, emojiItems} from './types' 42 43 export interface Avatar { ··· 183 let image = items[0] 184 if (!image) return 185 186 - if (!isWeb) { 187 try { 188 image = await openCropper({ 189 imageUri: image.path, ··· 200 201 // If we are on mobile, prefetching the image will load the image into memory before we try and display it, 202 // stopping any brief flickers. 203 - if (isNative) { 204 await ExpoImage.prefetch(image.path) 205 } 206
··· 17 import {logEvent, useGate} from '#/lib/statsig/statsig' 18 import {isCancelledError} from '#/lib/strings/errors' 19 import {logger} from '#/logger' 20 import { 21 OnboardingControls, 22 OnboardingDescriptionText, ··· 37 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' 38 import {CircleInfo_Stroke2_Corner0_Rounded} from '#/components/icons/CircleInfo' 39 import {Text} from '#/components/Typography' 40 + import {IS_NATIVE, IS_WEB} from '#/env' 41 import {type AvatarColor, avatarColors, type Emoji, emojiItems} from './types' 42 43 export interface Avatar { ··· 183 let image = items[0] 184 if (!image) return 185 186 + if (!IS_WEB) { 187 try { 188 image = await openCropper({ 189 imageUri: image.path, ··· 200 201 // If we are on mobile, prefetching the image will load the image into memory before we try and display it, 202 // stopping any brief flickers. 203 + if (IS_NATIVE) { 204 await ExpoImage.prefetch(image.path) 205 } 206
+5 -5
src/screens/Onboarding/StepSuggestedAccounts/index.tsx
··· 10 import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' 11 import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' 12 import {logger} from '#/logger' 13 - import {isWeb} from '#/platform/detection' 14 import {updateProfileShadow} from '#/state/cache/profile-shadow' 15 import {useLanguagePrefs} from '#/state/preferences' 16 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 31 import {Loader} from '#/components/Loader' 32 import * as ProfileCard from '#/components/ProfileCard' 33 import * as toast from '#/components/Toast' 34 import type * as bsky from '#/types/bsky' 35 import {bulkWriteFollows} from '../util' 36 ··· 161 style={[ 162 a.overflow_hidden, 163 a.mt_sm, 164 - isWeb 165 ? [a.max_w_full, web({minHeight: '100vh'})] 166 : {marginHorizontal: tokens.space.xl * -1}, 167 a.flex_1, ··· 213 a.mt_md, 214 a.border_y, 215 t.atoms.border_contrast_low, 216 - isWeb && [a.border_x, a.rounded_sm, a.overflow_hidden], 217 ]}> 218 {suggestedUsers?.actors.map((user, index) => ( 219 <SuggestedProfileCard ··· 324 ...interestsDisplayNames, 325 } 326 } 327 - gutterWidth={isWeb ? 0 : tokens.space.xl} 328 /> 329 ) 330 } ··· 350 const node = cardRef.current 351 if (!node || hasTrackedRef.current) return 352 353 - if (isWeb && typeof IntersectionObserver !== 'undefined') { 354 const observer = new IntersectionObserver( 355 entries => { 356 if (entries[0]?.isIntersecting && !hasTrackedRef.current) {
··· 10 import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' 11 import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' 12 import {logger} from '#/logger' 13 import {updateProfileShadow} from '#/state/cache/profile-shadow' 14 import {useLanguagePrefs} from '#/state/preferences' 15 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 30 import {Loader} from '#/components/Loader' 31 import * as ProfileCard from '#/components/ProfileCard' 32 import * as toast from '#/components/Toast' 33 + import {IS_WEB} from '#/env' 34 import type * as bsky from '#/types/bsky' 35 import {bulkWriteFollows} from '../util' 36 ··· 161 style={[ 162 a.overflow_hidden, 163 a.mt_sm, 164 + IS_WEB 165 ? [a.max_w_full, web({minHeight: '100vh'})] 166 : {marginHorizontal: tokens.space.xl * -1}, 167 a.flex_1, ··· 213 a.mt_md, 214 a.border_y, 215 t.atoms.border_contrast_low, 216 + IS_WEB && [a.border_x, a.rounded_sm, a.overflow_hidden], 217 ]}> 218 {suggestedUsers?.actors.map((user, index) => ( 219 <SuggestedProfileCard ··· 324 ...interestsDisplayNames, 325 } 326 } 327 + gutterWidth={IS_WEB ? 0 : tokens.space.xl} 328 /> 329 ) 330 } ··· 350 const node = cardRef.current 351 if (!node || hasTrackedRef.current) return 352 353 + if (IS_WEB && typeof IntersectionObserver !== 'undefined') { 354 const observer = new IntersectionObserver( 355 entries => { 356 if (entries[0]?.isIntersecting && !hasTrackedRef.current) {
+2 -2
src/screens/Onboarding/index.tsx
··· 4 5 import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' 6 import {useGate} from '#/lib/statsig/statsig' 7 - import {isNative} from '#/platform/detection' 8 import {useLanguagePrefs} from '#/state/preferences' 9 import { 10 Layout, ··· 24 import {useFindContactsFlowState} from '#/components/contacts/state' 25 import {Portal} from '#/components/Portal' 26 import {ScreenTransition} from '#/components/ScreenTransition' 27 import {ENV} from '#/env' 28 import {StepFindContacts} from './StepFindContacts' 29 import {StepFindContactsIntro} from './StepFindContactsIntro' ··· 50 useIsFindContactsFeatureEnabledBasedOnGeolocation() 51 const showFindContacts = 52 ENV !== 'e2e' && 53 - isNative && 54 findContactsEnabled && 55 !gate('disable_onboarding_find_contacts') 56
··· 4 5 import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' 6 import {useGate} from '#/lib/statsig/statsig' 7 import {useLanguagePrefs} from '#/state/preferences' 8 import { 9 Layout, ··· 23 import {useFindContactsFlowState} from '#/components/contacts/state' 24 import {Portal} from '#/components/Portal' 25 import {ScreenTransition} from '#/components/ScreenTransition' 26 + import {IS_NATIVE} from '#/env' 27 import {ENV} from '#/env' 28 import {StepFindContacts} from './StepFindContacts' 29 import {StepFindContactsIntro} from './StepFindContactsIntro' ··· 50 useIsFindContactsFeatureEnabledBasedOnGeolocation() 51 const showFindContacts = 52 ENV !== 'e2e' && 53 + IS_NATIVE && 54 findContactsEnabled && 55 !gate('disable_onboarding_find_contacts') 56
+7 -6
src/screens/PostThread/components/GrowthHack.tsx
··· 3 import {PrivacySensitive} from 'expo-privacy-sensitive' 4 5 import {useAppState} from '#/lib/hooks/useAppState' 6 - import {isIOS} from '#/platform/detection' 7 import {atoms as a, useTheme} from '#/alf' 8 import {sizes as iconSizes} from '#/components/icons/common' 9 import {Mark as Logo} from '#/components/icons/Logo' 10 11 const ICON_SIZE = 'xl' as const 12 ··· 25 26 const appState = useAppState() 27 28 - if (!isIOS || appState !== 'active') return children 29 30 return ( 31 <View ··· 33 a.relative, 34 a.justify_center, 35 align === 'right' ? a.align_end : a.align_start, 36 - width === undefined ? {opacity: 0} : {minWidth: width}, 37 ]}> 38 <PrivacySensitive 39 style={[ ··· 46 // when finding the size of the button, we need the containing 47 // element to have a concrete size otherwise the text will 48 // collapse to 0 width. so set it to a really big number 49 - // and hide the entire thing (see above) 50 - width === undefined && {width: 10000}, 51 ]}> 52 <View 53 onLayout={evt => setWidth(evt.nativeEvent.layout.width)} 54 style={[ 55 t.atoms.bg, 56 - // make sure it covers the icon! the won't always be a button 57 {minWidth: iconSizes[ICON_SIZE], minHeight: iconSizes[ICON_SIZE]}, 58 ]}> 59 {children}
··· 3 import {PrivacySensitive} from 'expo-privacy-sensitive' 4 5 import {useAppState} from '#/lib/hooks/useAppState' 6 import {atoms as a, useTheme} from '#/alf' 7 import {sizes as iconSizes} from '#/components/icons/common' 8 import {Mark as Logo} from '#/components/icons/Logo' 9 + import {IS_IOS} from '#/env' 10 11 const ICON_SIZE = 'xl' as const 12 ··· 25 26 const appState = useAppState() 27 28 + if (!IS_IOS || appState !== 'active') return children 29 30 return ( 31 <View ··· 33 a.relative, 34 a.justify_center, 35 align === 'right' ? a.align_end : a.align_start, 36 + {minWidth: width ?? iconSizes[ICON_SIZE]}, 37 ]}> 38 <PrivacySensitive 39 style={[ ··· 46 // when finding the size of the button, we need the containing 47 // element to have a concrete size otherwise the text will 48 // collapse to 0 width. so set it to a really big number 49 + // and just use `pointer-events: box-none` so it doesn't interfere with the UI 50 + {width: 1000}, 51 + a.pointer_events_box_none, 52 ]}> 53 <View 54 onLayout={evt => setWidth(evt.nativeEvent.layout.width)} 55 style={[ 56 t.atoms.bg, 57 + // make sure it covers the icon! children might be undefined 58 {minWidth: iconSizes[ICON_SIZE], minHeight: iconSizes[ICON_SIZE]}, 59 ]}> 60 {children}
+2 -2
src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx
··· 5 import {useNavigation} from '@react-navigation/native' 6 7 import {logger} from '#/logger' 8 - import {isIOS} from '#/platform/detection' 9 import {useProfileShadow} from '#/state/cache/profile-shadow' 10 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11 import { ··· 18 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 19 import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 20 import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 21 import {GrowthHack} from './GrowthHack' 22 23 export function ThreadItemAnchorFollowButton({did}: {did: string}) { 24 - if (isIOS) { 25 return ( 26 <GrowthHack> 27 <ThreadItemAnchorFollowButtonInner did={did} />
··· 5 import {useNavigation} from '@react-navigation/native' 6 7 import {logger} from '#/logger' 8 import {useProfileShadow} from '#/state/cache/profile-shadow' 9 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 10 import { ··· 17 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 18 import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 19 import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 20 + import {IS_IOS} from '#/env' 21 import {GrowthHack} from './GrowthHack' 22 23 export function ThreadItemAnchorFollowButton({did}: {did: string}) { 24 + if (IS_IOS) { 25 return ( 26 <GrowthHack> 27 <ThreadItemAnchorFollowButtonInner did={did} />
-1
src/screens/PostThread/index.tsx
··· 64 */ 65 const thread = usePostThread({anchor: uri}) 66 const {anchor, hasParents} = useMemo(() => { 67 - // eslint-disable-next-line @typescript-eslint/no-shadow 68 let hasParents = false 69 for (const item of thread.data.items) { 70 if (item.type === 'threadPost' && item.depth === 0) {
··· 64 */ 65 const thread = usePostThread({anchor: uri}) 66 const {anchor, hasParents} = useMemo(() => { 67 let hasParents = false 68 for (const item of thread.data.items) { 69 if (item.type === 'threadPost' && item.depth === 0) {
+2 -2
src/screens/Profile/Header/GrowableAvatar.tsx
··· 7 } from 'react-native-reanimated' 8 import type React from 'react' 9 10 - import {isIOS} from '#/platform/detection' 11 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 12 13 export function GrowableAvatar({ 14 children, ··· 20 const pagerContext = usePagerHeaderContext() 21 22 // pagerContext should only be present on iOS, but better safe than sorry 23 - if (!pagerContext || !isIOS) { 24 return <View style={style}>{children}</View> 25 } 26
··· 7 } from 'react-native-reanimated' 8 import type React from 'react' 9 10 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 11 + import {IS_IOS} from '#/env' 12 13 export function GrowableAvatar({ 14 children, ··· 20 const pagerContext = usePagerHeaderContext() 21 22 // pagerContext should only be present on iOS, but better safe than sorry 23 + if (!pagerContext || !IS_IOS) { 24 return <View style={style}>{children}</View> 25 } 26
+3 -3
src/screens/Profile/Header/GrowableBanner.tsx
··· 15 import {useIsFetching} from '@tanstack/react-query' 16 import type React from 'react' 17 18 - import {isIOS} from '#/platform/detection' 19 import {RQKEY_ROOT as STARTERPACK_RQKEY_ROOT} from '#/state/queries/actor-starter-packs' 20 import {RQKEY_ROOT as FEED_RQKEY_ROOT} from '#/state/queries/post-feed' 21 import {RQKEY_ROOT as FEEDGEN_RQKEY_ROOT} from '#/state/queries/profile-feedgens' 22 import {RQKEY_ROOT as LIST_RQKEY_ROOT} from '#/state/queries/profile-lists' 23 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 24 import {atoms as a} from '#/alf' 25 26 const AnimatedBlurView = Animated.createAnimatedComponent(BlurView) 27 ··· 39 const pagerContext = usePagerHeaderContext() 40 41 // plain non-growable mode for Android/Web 42 - if (!pagerContext || !isIOS) { 43 return ( 44 <Pressable 45 onPress={onPress} ··· 164 style={[ 165 a.absolute, 166 a.inset_0, 167 - {top: topInset - (isIOS ? 15 : 0)}, 168 a.justify_center, 169 a.align_center, 170 ]}>
··· 15 import {useIsFetching} from '@tanstack/react-query' 16 import type React from 'react' 17 18 import {RQKEY_ROOT as STARTERPACK_RQKEY_ROOT} from '#/state/queries/actor-starter-packs' 19 import {RQKEY_ROOT as FEED_RQKEY_ROOT} from '#/state/queries/post-feed' 20 import {RQKEY_ROOT as FEEDGEN_RQKEY_ROOT} from '#/state/queries/profile-feedgens' 21 import {RQKEY_ROOT as LIST_RQKEY_ROOT} from '#/state/queries/profile-lists' 22 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 23 import {atoms as a} from '#/alf' 24 + import {IS_IOS} from '#/env' 25 26 const AnimatedBlurView = Animated.createAnimatedComponent(BlurView) 27 ··· 39 const pagerContext = usePagerHeaderContext() 40 41 // plain non-growable mode for Android/Web 42 + if (!pagerContext || !IS_IOS) { 43 return ( 44 <Pressable 45 onPress={onPress} ··· 164 style={[ 165 a.absolute, 166 a.inset_0, 167 + {top: topInset - (IS_IOS ? 15 : 0)}, 168 a.justify_center, 169 a.align_center, 170 ]}>
+3 -3
src/screens/Profile/Header/Handle.tsx
··· 4 import {useLingui} from '@lingui/react' 5 6 import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' 7 - import {isIOS, isNative} from '#/platform/detection' 8 import {type Shadow} from '#/state/cache/types' 9 import {useShowLinkInHandle} from '#/state/preferences/show-link-in-handle.tsx' 10 import {atoms as a, useTheme, web} from '#/alf' 11 import {InlineLinkText} from '#/components/Link.tsx' 12 import {NewskieDialog} from '#/components/NewskieDialog' 13 import {Text} from '#/components/Typography' 14 15 export function ProfileHeaderHandle({ 16 profile, ··· 28 profile.handle, 29 '@', 30 // forceLTR handled by CSS above on web 31 - isNative, 32 ) 33 return ( 34 <View 35 style={[a.flex_row, a.gap_sm, a.align_center, {maxWidth: '100%'}]} 36 - pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}> 37 <NewskieDialog profile={profile} disabled={disableTaps} /> 38 39 <Text
··· 4 import {useLingui} from '@lingui/react' 5 6 import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' 7 import {type Shadow} from '#/state/cache/types' 8 import {useShowLinkInHandle} from '#/state/preferences/show-link-in-handle.tsx' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {InlineLinkText} from '#/components/Link.tsx' 11 import {NewskieDialog} from '#/components/NewskieDialog' 12 import {Text} from '#/components/Typography' 13 + import {IS_IOS, IS_NATIVE} from '#/env' 14 15 export function ProfileHeaderHandle({ 16 profile, ··· 28 profile.handle, 29 '@', 30 // forceLTR handled by CSS above on web 31 + IS_NATIVE, 32 ) 33 return ( 34 <View 35 style={[a.flex_row, a.gap_sm, a.align_center, {maxWidth: '100%'}]} 36 + pointerEvents={disableTaps ? 'none' : IS_IOS ? 'auto' : 'box-none'}> 37 <NewskieDialog profile={profile} disabled={disableTaps} /> 38 39 <Text
+3 -3
src/screens/Profile/Header/ProfileHeaderLabeler.tsx
··· 15 import {useHaptics} from '#/lib/haptics' 16 import {isAppLabeler} from '#/lib/moderation' 17 import {logger} from '#/logger' 18 - import {isIOS} from '#/platform/detection' 19 import {useProfileShadow} from '#/state/cache/profile-shadow' 20 import {type Shadow} from '#/state/cache/types' 21 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' ··· 36 import {RichText} from '#/components/RichText' 37 import * as Toast from '#/components/Toast' 38 import {Text} from '#/components/Typography' 39 import {ProfileHeaderDisplayName} from './DisplayName' 40 import {EditProfileDialog} from './EditProfileDialog' 41 import {ProfileHeaderHandle} from './Handle' ··· 114 isPlaceholderProfile={isPlaceholderProfile}> 115 <View 116 style={[a.px_lg, a.pt_md, a.pb_sm]} 117 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 118 <View 119 style={[a.flex_row, a.justify_end, a.align_center, a.gap_xs, a.pb_lg]} 120 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 121 <HeaderLabelerButtons profile={profile} /> 122 </View> 123 <View style={[a.flex_col, a.gap_2xs, a.pt_2xs, a.pb_md]}>
··· 15 import {useHaptics} from '#/lib/haptics' 16 import {isAppLabeler} from '#/lib/moderation' 17 import {logger} from '#/logger' 18 import {useProfileShadow} from '#/state/cache/profile-shadow' 19 import {type Shadow} from '#/state/cache/types' 20 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' ··· 35 import {RichText} from '#/components/RichText' 36 import * as Toast from '#/components/Toast' 37 import {Text} from '#/components/Typography' 38 + import {IS_IOS} from '#/env' 39 import {ProfileHeaderDisplayName} from './DisplayName' 40 import {EditProfileDialog} from './EditProfileDialog' 41 import {ProfileHeaderHandle} from './Handle' ··· 114 isPlaceholderProfile={isPlaceholderProfile}> 115 <View 116 style={[a.px_lg, a.pt_md, a.pb_sm]} 117 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 118 <View 119 style={[a.flex_row, a.justify_end, a.align_center, a.gap_xs, a.pb_lg]} 120 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 121 <HeaderLabelerButtons profile={profile} /> 122 </View> 123 <View style={[a.flex_col, a.gap_2xs, a.pt_2xs, a.pb_md]}>
+3 -3
src/screens/Profile/Header/ProfileHeaderStandard.tsx
··· 15 import {sanitizeDisplayName} from '#/lib/strings/display-names' 16 import {sanitizeHandle} from '#/lib/strings/handles' 17 import {logger} from '#/logger' 18 - import {isIOS} from '#/platform/detection' 19 import {type Shadow, useProfileShadow} from '#/state/cache/profile-shadow' 20 import {useDisableFollowedByMetrics} from '#/state/preferences/disable-followed-by-metrics' 21 import { ··· 40 import * as Toast from '#/components/Toast' 41 import {Text} from '#/components/Typography' 42 import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' 43 import {EditProfileDialog} from './EditProfileDialog' 44 import {ProfileHeaderHandle} from './Handle' 45 import {ProfileHeaderMetrics} from './Metrics' ··· 107 isPlaceholderProfile={isPlaceholderProfile}> 108 <View 109 style={[a.px_lg, a.pt_md, a.pb_sm, a.overflow_hidden]} 110 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 111 <View 112 style={[ 113 {paddingLeft: 90}, ··· 118 a.pb_sm, 119 a.flex_wrap, 120 ]} 121 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 122 <HeaderStandardButtons 123 profile={profile} 124 moderation={moderation}
··· 15 import {sanitizeDisplayName} from '#/lib/strings/display-names' 16 import {sanitizeHandle} from '#/lib/strings/handles' 17 import {logger} from '#/logger' 18 import {type Shadow, useProfileShadow} from '#/state/cache/profile-shadow' 19 import {useDisableFollowedByMetrics} from '#/state/preferences/disable-followed-by-metrics' 20 import { ··· 39 import * as Toast from '#/components/Toast' 40 import {Text} from '#/components/Typography' 41 import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' 42 + import {IS_IOS} from '#/env' 43 import {EditProfileDialog} from './EditProfileDialog' 44 import {ProfileHeaderHandle} from './Handle' 45 import {ProfileHeaderMetrics} from './Metrics' ··· 107 isPlaceholderProfile={isPlaceholderProfile}> 108 <View 109 style={[a.px_lg, a.pt_md, a.pb_sm, a.overflow_hidden]} 110 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 111 <View 112 style={[ 113 {paddingLeft: 90}, ··· 118 a.pb_sm, 119 a.flex_wrap, 120 ]} 121 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 122 <HeaderStandardButtons 123 profile={profile} 124 moderation={moderation}
+5 -5
src/screens/Profile/Header/Shell.tsx
··· 19 import {useHaptics} from '#/lib/haptics' 20 import {type NavigationProp} from '#/lib/routes/types' 21 import {logger} from '#/logger' 22 - import {isIOS} from '#/platform/detection' 23 import {type Shadow} from '#/state/cache/types' 24 import {useLightboxControls} from '#/state/lightbox' 25 import {useEnableSquareAvatars} from '#/state/preferences/enable-square-avatars' ··· 41 import {LiveStatusDialog} from '#/components/live/LiveStatusDialog' 42 import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' 43 import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' 44 import {GrowableAvatar} from './GrowableAvatar' 45 import {GrowableBanner} from './GrowableBanner' 46 import {StatusBarShadow} from './StatusBarShadow' ··· 196 }, [profile.banner, moderation, _openLightboxBanner, bannerRef]) 197 198 return ( 199 - <View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}> 200 <View 201 - pointerEvents={isIOS ? 'auto' : 'box-none'} 202 style={[a.relative, {height: 150}]}> 203 <StatusBarShadow /> 204 <GrowableBanner ··· 285 a.px_lg, 286 a.pt_xs, 287 a.pb_sm, 288 - isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 289 ]} 290 /> 291 ) : ( ··· 295 a.px_lg, 296 a.pt_xs, 297 a.pb_sm, 298 - isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 299 ]} 300 /> 301 ))}
··· 19 import {useHaptics} from '#/lib/haptics' 20 import {type NavigationProp} from '#/lib/routes/types' 21 import {logger} from '#/logger' 22 import {type Shadow} from '#/state/cache/types' 23 import {useLightboxControls} from '#/state/lightbox' 24 import {useEnableSquareAvatars} from '#/state/preferences/enable-square-avatars' ··· 40 import {LiveStatusDialog} from '#/components/live/LiveStatusDialog' 41 import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' 42 import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' 43 + import {IS_IOS} from '#/env' 44 import {GrowableAvatar} from './GrowableAvatar' 45 import {GrowableBanner} from './GrowableBanner' 46 import {StatusBarShadow} from './StatusBarShadow' ··· 196 }, [profile.banner, moderation, _openLightboxBanner, bannerRef]) 197 198 return ( 199 + <View style={t.atoms.bg} pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 200 <View 201 + pointerEvents={IS_IOS ? 'auto' : 'box-none'} 202 style={[a.relative, {height: 150}]}> 203 <StatusBarShadow /> 204 <GrowableBanner ··· 285 a.px_lg, 286 a.pt_xs, 287 a.pb_sm, 288 + IS_IOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 289 ]} 290 /> 291 ) : ( ··· 295 a.px_lg, 296 a.pt_xs, 297 a.pb_sm, 298 + IS_IOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 299 ]} 300 /> 301 ))}
+2 -2
src/screens/Profile/Header/StatusBarShadow.tsx
··· 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 6 import {LinearGradient} from 'expo-linear-gradient' 7 8 - import {isIOS} from '#/platform/detection' 9 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 10 import {atoms as a} from '#/alf' 11 12 const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient) 13 ··· 15 const {top: topInset} = useSafeAreaInsets() 16 const pagerContext = usePagerHeaderContext() 17 18 - if (isIOS && pagerContext) { 19 const {scrollY} = pagerContext 20 return <StatusBarShadowInnner scrollY={scrollY} /> 21 }
··· 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 6 import {LinearGradient} from 'expo-linear-gradient' 7 8 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 9 import {atoms as a} from '#/alf' 10 + import {IS_IOS} from '#/env' 11 12 const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient) 13 ··· 15 const {top: topInset} = useSafeAreaInsets() 16 const pagerContext = usePagerHeaderContext() 17 18 + if (IS_IOS && pagerContext) { 19 const {scrollY} = pagerContext 20 return <StatusBarShadowInnner scrollY={scrollY} /> 21 }
+2 -2
src/screens/Profile/Header/SuggestedFollows.tsx
··· 2 import {type AppBskyActorDefs} from '@atproto/api' 3 4 import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation' 5 - import {isAndroid} from '#/platform/detection' 6 import {useModerationOpts} from '#/state/preferences/moderation-opts' 7 import { 8 useSuggestedFollowsByActorQuery, ··· 10 } from '#/state/queries/suggested-follows' 11 import {useBreakpoints} from '#/alf' 12 import {ProfileGrid} from '#/components/FeedInterstitials' 13 14 const DISMISS_ANIMATION_DURATION = 200 15 ··· 210 * This issue stems from Android not allowing dragging on clickable elements in the profile header. 211 * Blocking the ability to scroll on Android is too much of a trade-off for now. 212 **/ 213 - if (isAndroid) return null 214 215 return ( 216 <AccordionAnimation isExpanded={isExpanded}>
··· 2 import {type AppBskyActorDefs} from '@atproto/api' 3 4 import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation' 5 import {useModerationOpts} from '#/state/preferences/moderation-opts' 6 import { 7 useSuggestedFollowsByActorQuery, ··· 9 } from '#/state/queries/suggested-follows' 10 import {useBreakpoints} from '#/alf' 11 import {ProfileGrid} from '#/components/FeedInterstitials' 12 + import {IS_ANDROID} from '#/env' 13 14 const DISMISS_ANIMATION_DURATION = 200 15 ··· 210 * This issue stems from Android not allowing dragging on clickable elements in the profile header. 211 * Blocking the ability to scroll on Android is too much of a trade-off for now. 212 **/ 213 + if (IS_ANDROID) return null 214 215 return ( 216 <AccordionAnimation isExpanded={isExpanded}>
+2 -2
src/screens/Profile/Header/index.tsx
··· 17 import {useIsFocused} from '@react-navigation/native' 18 19 import {sanitizeHandle} from '#/lib/strings/handles' 20 - import {isNative} from '#/platform/detection' 21 import {useProfileShadow} from '#/state/cache/profile-shadow' 22 import {useModerationOpts} from '#/state/preferences/moderation-opts' 23 import {useSetLightStatusBar} from '#/state/shell/light-status-bar' ··· 26 import {atoms as a, useTheme} from '#/alf' 27 import {Header} from '#/components/Layout' 28 import * as ProfileCard from '#/components/ProfileCard' 29 import { 30 HeaderLabelerButtons, 31 ProfileHeaderLabeler, ··· 83 84 return ( 85 <> 86 - {isNative && ( 87 <MinimalHeader 88 onLayout={evt => setMinimumHeight(evt.nativeEvent.layout.height)} 89 profile={props.profile}
··· 17 import {useIsFocused} from '@react-navigation/native' 18 19 import {sanitizeHandle} from '#/lib/strings/handles' 20 import {useProfileShadow} from '#/state/cache/profile-shadow' 21 import {useModerationOpts} from '#/state/preferences/moderation-opts' 22 import {useSetLightStatusBar} from '#/state/shell/light-status-bar' ··· 25 import {atoms as a, useTheme} from '#/alf' 26 import {Header} from '#/components/Layout' 27 import * as ProfileCard from '#/components/ProfileCard' 28 + import {IS_NATIVE} from '#/env' 29 import { 30 HeaderLabelerButtons, 31 ProfileHeaderLabeler, ··· 83 84 return ( 85 <> 86 + {IS_NATIVE && ( 87 <MinimalHeader 88 onLayout={evt => setMinimumHeight(evt.nativeEvent.layout.height)} 89 profile={props.profile}
+3 -3
src/screens/Profile/ProfileFeed/index.tsx
··· 17 import {type NavigationProp} from '#/lib/routes/types' 18 import {makeRecordUri} from '#/lib/strings/url-helpers' 19 import {s} from '#/lib/styles' 20 - import {isNative} from '#/platform/detection' 21 import {listenSoftReset} from '#/state/events' 22 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 23 import { ··· 47 } from '#/screens/Profile/components/ProfileFeedHeader' 48 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 49 import * as Layout from '#/components/Layout' 50 51 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'> 52 export function ProfileFeedScreen(props: Props) { ··· 175 176 const onScrollToTop = useCallback(() => { 177 scrollElRef.current?.scrollToOffset({ 178 - animated: isNative, 179 offset: 0, // -headerHeight, 180 }) 181 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) ··· 204 const feedIsVideoMode = 205 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO 206 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode 207 - return isNative && _isVideoFeed 208 }, [feedInfo]) 209 210 return (
··· 17 import {type NavigationProp} from '#/lib/routes/types' 18 import {makeRecordUri} from '#/lib/strings/url-helpers' 19 import {s} from '#/lib/styles' 20 import {listenSoftReset} from '#/state/events' 21 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 22 import { ··· 46 } from '#/screens/Profile/components/ProfileFeedHeader' 47 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 48 import * as Layout from '#/components/Layout' 49 + import {IS_NATIVE} from '#/env' 50 51 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'> 52 export function ProfileFeedScreen(props: Props) { ··· 175 176 const onScrollToTop = useCallback(() => { 177 scrollElRef.current?.scrollToOffset({ 178 + animated: IS_NATIVE, 179 offset: 0, // -headerHeight, 180 }) 181 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) ··· 204 const feedIsVideoMode = 205 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO 206 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode 207 + return IS_NATIVE && _isVideoFeed 208 }, [feedInfo]) 209 210 return (
+4 -4
src/screens/Profile/Sections/Feed.tsx
··· 5 import {useQueryClient} from '@tanstack/react-query' 6 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 - import {isIOS, isNative} from '#/platform/detection' 9 import { 10 type FeedDescriptor, 11 RQKEY as FEED_RQKEY, ··· 21 import {atoms as a, ios, useTheme} from '#/alf' 22 import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig' 23 import {Text} from '#/components/Typography' 24 import {type SectionRef} from './types' 25 26 interface FeedSectionProps { ··· 53 const [hasNew, setHasNew] = useState(false) 54 const [isScrolledDown, setIsScrolledDown] = useState(false) 55 const shouldUseAdjustedNumToRender = feed.endsWith('posts_and_author_threads') 56 - const isVideoFeed = isNative && feed.endsWith('posts_with_video') 57 const adjustedInitialNumToRender = useInitialNumToRender({ 58 screenHeightOffset: headerHeight, 59 }) 60 const onScrollToTop = useCallback(() => { 61 scrollElRef.current?.scrollToOffset({ 62 - animated: isNative, 63 offset: -headerHeight, 64 }) 65 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) ··· 85 }, [_, emptyStateButton, emptyStateIcon, emptyStateMessage]) 86 87 useEffect(() => { 88 - if (isIOS && isFocused && scrollElRef.current) { 89 const nativeTag = findNodeHandle(scrollElRef.current) 90 setScrollViewTag(nativeTag) 91 }
··· 5 import {useQueryClient} from '@tanstack/react-query' 6 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 import { 9 type FeedDescriptor, 10 RQKEY as FEED_RQKEY, ··· 20 import {atoms as a, ios, useTheme} from '#/alf' 21 import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig' 22 import {Text} from '#/components/Typography' 23 + import {IS_IOS, IS_NATIVE} from '#/env' 24 import {type SectionRef} from './types' 25 26 interface FeedSectionProps { ··· 53 const [hasNew, setHasNew] = useState(false) 54 const [isScrolledDown, setIsScrolledDown] = useState(false) 55 const shouldUseAdjustedNumToRender = feed.endsWith('posts_and_author_threads') 56 + const isVideoFeed = IS_NATIVE && feed.endsWith('posts_with_video') 57 const adjustedInitialNumToRender = useInitialNumToRender({ 58 screenHeightOffset: headerHeight, 59 }) 60 const onScrollToTop = useCallback(() => { 61 scrollElRef.current?.scrollToOffset({ 62 + animated: IS_NATIVE, 63 offset: -headerHeight, 64 }) 65 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) ··· 85 }, [_, emptyStateButton, emptyStateIcon, emptyStateMessage]) 86 87 useEffect(() => { 88 + if (IS_IOS && isFocused && scrollElRef.current) { 89 const nativeTag = findNodeHandle(scrollElRef.current) 90 setScrollViewTag(nativeTag) 91 }
+3 -3
src/screens/Profile/Sections/Labels.tsx
··· 10 import {useLingui} from '@lingui/react' 11 12 import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation' 13 - import {isIOS, isNative} from '#/platform/detection' 14 import {List, type ListRef} from '#/view/com/util/List' 15 import {atoms as a, ios, tokens, useTheme} from '#/alf' 16 import {Divider} from '#/components/Divider' ··· 19 import {Loader} from '#/components/Loader' 20 import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' 21 import {Text} from '#/components/Typography' 22 import {ErrorState} from '../ErrorState' 23 import {type SectionRef} from './types' 24 ··· 49 50 const onScrollToTop = useCallback(() => { 51 scrollElRef.current?.scrollToOffset({ 52 - animated: isNative, 53 offset: -headerHeight, 54 }) 55 }, [scrollElRef, headerHeight]) ··· 59 })) 60 61 useEffect(() => { 62 - if (isIOS && isFocused && scrollElRef.current) { 63 const nativeTag = findNodeHandle(scrollElRef.current) 64 setScrollViewTag(nativeTag) 65 }
··· 10 import {useLingui} from '@lingui/react' 11 12 import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation' 13 import {List, type ListRef} from '#/view/com/util/List' 14 import {atoms as a, ios, tokens, useTheme} from '#/alf' 15 import {Divider} from '#/components/Divider' ··· 18 import {Loader} from '#/components/Loader' 19 import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' 20 import {Text} from '#/components/Typography' 21 + import {IS_IOS, IS_NATIVE} from '#/env' 22 import {ErrorState} from '../ErrorState' 23 import {type SectionRef} from './types' 24 ··· 49 50 const onScrollToTop = useCallback(() => { 51 scrollElRef.current?.scrollToOffset({ 52 + animated: IS_NATIVE, 53 offset: -headerHeight, 54 }) 55 }, [scrollElRef, headerHeight]) ··· 59 })) 60 61 useEffect(() => { 62 + if (IS_IOS && isFocused && scrollElRef.current) { 63 const nativeTag = findNodeHandle(scrollElRef.current) 64 setScrollViewTag(nativeTag) 65 }
+3 -3
src/screens/Profile/components/ProfileFeedHeader.tsx
··· 11 import {sanitizeHandle} from '#/lib/strings/handles' 12 import {toShareUrl} from '#/lib/strings/url-helpers' 13 import {logger} from '#/logger' 14 - import {isWeb} from '#/platform/detection' 15 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 16 import {type FeedSourceFeedInfo} from '#/state/queries/feed' 17 import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' ··· 53 } from '#/components/moderation/ReportDialog' 54 import {RichText} from '#/components/RichText' 55 import {Text} from '#/components/Typography' 56 57 export function ProfileFeedHeaderSkeleton() { 58 const t = useTheme() ··· 194 style={[ 195 a.justify_start, 196 { 197 - paddingVertical: isWeb ? 2 : 4, 198 paddingRight: 8, 199 }, 200 ]} ··· 213 t.atoms.bg_contrast_25, 214 { 215 opacity: 0, 216 - left: isWeb ? -2 : -4, 217 right: 0, 218 }, 219 pressed && {
··· 11 import {sanitizeHandle} from '#/lib/strings/handles' 12 import {toShareUrl} from '#/lib/strings/url-helpers' 13 import {logger} from '#/logger' 14 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 15 import {type FeedSourceFeedInfo} from '#/state/queries/feed' 16 import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' ··· 52 } from '#/components/moderation/ReportDialog' 53 import {RichText} from '#/components/RichText' 54 import {Text} from '#/components/Typography' 55 + import {IS_WEB} from '#/env' 56 57 export function ProfileFeedHeaderSkeleton() { 58 const t = useTheme() ··· 194 style={[ 195 a.justify_start, 196 { 197 + paddingVertical: IS_WEB ? 2 : 4, 198 paddingRight: 8, 199 }, 200 ]} ··· 213 t.atoms.bg_contrast_25, 214 { 215 opacity: 0, 216 + left: IS_WEB ? -2 : -4, 217 right: 0, 218 }, 219 pressed && {
+2 -2
src/screens/ProfileList/AboutSection.tsx
··· 4 import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 - import {isNative} from '#/platform/detection' 8 import {useSession} from '#/state/session' 9 import {ListMembers} from '#/view/com/lists/ListMembers' 10 import {EmptyState} from '#/view/com/util/EmptyState' ··· 14 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 16 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 17 18 interface SectionRef { 19 scrollToTop: () => void ··· 42 43 const onScrollToTop = useCallback(() => { 44 scrollElRef.current?.scrollToOffset({ 45 - animated: isNative, 46 offset: -headerHeight, 47 }) 48 }, [scrollElRef, headerHeight])
··· 4 import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 import {useSession} from '#/state/session' 8 import {ListMembers} from '#/view/com/lists/ListMembers' 9 import {EmptyState} from '#/view/com/util/EmptyState' ··· 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 15 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 16 + import {IS_NATIVE} from '#/env' 17 18 interface SectionRef { 19 scrollToTop: () => void ··· 42 43 const onScrollToTop = useCallback(() => { 44 scrollElRef.current?.scrollToOffset({ 45 + animated: IS_NATIVE, 46 offset: -headerHeight, 47 }) 48 }, [scrollElRef, headerHeight])
+2 -2
src/screens/ProfileList/FeedSection.tsx
··· 5 import {useIsFocused} from '@react-navigation/native' 6 import {useQueryClient} from '@tanstack/react-query' 7 8 - import {isNative} from '#/platform/detection' 9 import {listenSoftReset} from '#/state/events' 10 import { 11 type FeedDescriptor, ··· 19 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 20 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 21 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 22 23 interface SectionRef { 24 scrollToTop: () => void ··· 51 52 const onScrollToTop = useCallback(() => { 53 scrollElRef.current?.scrollToOffset({ 54 - animated: isNative, 55 offset: -headerHeight, 56 }) 57 queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
··· 5 import {useIsFocused} from '@react-navigation/native' 6 import {useQueryClient} from '@tanstack/react-query' 7 8 import {listenSoftReset} from '#/state/events' 9 import { 10 type FeedDescriptor, ··· 18 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 19 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 20 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 21 + import {IS_NATIVE} from '#/env' 22 23 interface SectionRef { 24 scrollToTop: () => void ··· 51 52 const onScrollToTop = useCallback(() => { 53 scrollElRef.current?.scrollToOffset({ 54 + animated: IS_NATIVE, 55 offset: -headerHeight, 56 }) 57 queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
+4 -4
src/screens/ProfileList/components/MoreOptionsMenu.tsx
··· 7 import {shareUrl} from '#/lib/sharing' 8 import {toShareUrl} from '#/lib/strings/url-helpers' 9 import {logger} from '#/logger' 10 - import {isWeb} from '#/platform/detection' 11 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 12 import { 13 useListBlockMutation, ··· 35 } from '#/components/moderation/ReportDialog' 36 import * as Prompt from '#/components/Prompt' 37 import * as Toast from '#/components/Toast' 38 39 export function MoreOptionsMenu({ 40 list, ··· 165 <Menu.Outer> 166 <Menu.Group> 167 <Menu.Item 168 - label={isWeb ? _(msg`Copy link to list`) : _(msg`Share via...`)} 169 onPress={onPressShare}> 170 <Menu.ItemText> 171 - {isWeb ? ( 172 <Trans>Copy link to list</Trans> 173 ) : ( 174 <Trans>Share via...</Trans> ··· 176 </Menu.ItemText> 177 <Menu.ItemIcon 178 position="right" 179 - icon={isWeb ? ChainLink : ShareIcon} 180 /> 181 </Menu.Item> 182 {savedFeedConfig && (
··· 7 import {shareUrl} from '#/lib/sharing' 8 import {toShareUrl} from '#/lib/strings/url-helpers' 9 import {logger} from '#/logger' 10 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11 import { 12 useListBlockMutation, ··· 34 } from '#/components/moderation/ReportDialog' 35 import * as Prompt from '#/components/Prompt' 36 import * as Toast from '#/components/Toast' 37 + import {IS_WEB} from '#/env' 38 39 export function MoreOptionsMenu({ 40 list, ··· 165 <Menu.Outer> 166 <Menu.Group> 167 <Menu.Item 168 + label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)} 169 onPress={onPressShare}> 170 <Menu.ItemText> 171 + {IS_WEB ? ( 172 <Trans>Copy link to list</Trans> 173 ) : ( 174 <Trans>Share via...</Trans> ··· 176 </Menu.ItemText> 177 <Menu.ItemIcon 178 position="right" 179 + icon={IS_WEB ? ChainLink : ShareIcon} 180 /> 181 </Menu.Item> 182 {savedFeedConfig && (
+11 -11
src/screens/Search/Shell.tsx
··· 22 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 23 import {MagnifyingGlassIcon} from '#/lib/icons' 24 import {type NavigationProp} from '#/lib/routes/types' 25 - import {isWeb} from '#/platform/detection' 26 import {listenSoftReset} from '#/state/events' 27 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 28 import { ··· 41 import {SearchInput} from '#/components/forms/SearchInput' 42 import * as Layout from '#/components/Layout' 43 import {Text} from '#/components/Typography' 44 import {account, useStorage} from '#/storage' 45 import type * as bsky from '#/types/bsky' 46 import {AutocompleteResults} from './components/AutocompleteResults' ··· 141 const [headerHeight, setHeaderHeight] = useState(0) 142 const headerRef = useRef(null) 143 useLayoutEffect(() => { 144 - if (isWeb) { 145 if (!headerRef.current) return 146 const measurement = (headerRef.current as Element).getBoundingClientRect() 147 setHeaderHeight(measurement.height) ··· 150 151 useFocusEffect( 152 useNonReactiveCallback(() => { 153 - if (isWeb) { 154 setSearchText(queryParam) 155 } 156 }), ··· 173 setShowAutocomplete(false) 174 updateSearchHistory(item) 175 176 - if (isWeb) { 177 // @ts-expect-error route is not typesafe 178 navigation.push(route.name, {...route.params, q: item}) 179 } else { ··· 188 scrollToTopWeb() 189 textInput.current?.blur() 190 setShowAutocomplete(false) 191 - if (isWeb) { 192 // Empty params resets the URL to be /search rather than /search?q= 193 // Also clear the tab parameter 194 const { ··· 211 }, [navigateToItem, searchText]) 212 213 const onAutocompleteResultPress = useCallback(() => { 214 - if (isWeb) { 215 setShowAutocomplete(false) 216 } else { 217 textInput.current?.blur() ··· 238 ) 239 240 const onSoftReset = useCallback(() => { 241 - if (isWeb) { 242 // Empty params resets the URL to be /search rather than /search?q= 243 // Also clear the tab parameter when soft resetting 244 const { ··· 265 ) 266 267 const onSearchInputFocus = useCallback(() => { 268 - if (isWeb) { 269 // Prevent a jump on iPad by ensuring that 270 // the initial focused render has no result list. 271 requestAnimationFrame(() => { ··· 282 283 // If a tab is specified, set the tab parameter 284 if (tab) { 285 - if (isWeb) { 286 navigation.setParams({...route.params, tab}) 287 } else { 288 navigation.setParams({tab}) ··· 299 <View 300 ref={headerRef} 301 onLayout={evt => { 302 - if (isWeb) setHeaderHeight(evt.nativeEvent.layout.height) 303 }} 304 style={[ 305 a.relative, ··· 581 } 582 583 function scrollToTopWeb() { 584 - if (isWeb) { 585 window.scrollTo(0, 0) 586 } 587 }
··· 22 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 23 import {MagnifyingGlassIcon} from '#/lib/icons' 24 import {type NavigationProp} from '#/lib/routes/types' 25 import {listenSoftReset} from '#/state/events' 26 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 27 import { ··· 40 import {SearchInput} from '#/components/forms/SearchInput' 41 import * as Layout from '#/components/Layout' 42 import {Text} from '#/components/Typography' 43 + import {IS_WEB} from '#/env' 44 import {account, useStorage} from '#/storage' 45 import type * as bsky from '#/types/bsky' 46 import {AutocompleteResults} from './components/AutocompleteResults' ··· 141 const [headerHeight, setHeaderHeight] = useState(0) 142 const headerRef = useRef(null) 143 useLayoutEffect(() => { 144 + if (IS_WEB) { 145 if (!headerRef.current) return 146 const measurement = (headerRef.current as Element).getBoundingClientRect() 147 setHeaderHeight(measurement.height) ··· 150 151 useFocusEffect( 152 useNonReactiveCallback(() => { 153 + if (IS_WEB) { 154 setSearchText(queryParam) 155 } 156 }), ··· 173 setShowAutocomplete(false) 174 updateSearchHistory(item) 175 176 + if (IS_WEB) { 177 // @ts-expect-error route is not typesafe 178 navigation.push(route.name, {...route.params, q: item}) 179 } else { ··· 188 scrollToTopWeb() 189 textInput.current?.blur() 190 setShowAutocomplete(false) 191 + if (IS_WEB) { 192 // Empty params resets the URL to be /search rather than /search?q= 193 // Also clear the tab parameter 194 const { ··· 211 }, [navigateToItem, searchText]) 212 213 const onAutocompleteResultPress = useCallback(() => { 214 + if (IS_WEB) { 215 setShowAutocomplete(false) 216 } else { 217 textInput.current?.blur() ··· 238 ) 239 240 const onSoftReset = useCallback(() => { 241 + if (IS_WEB) { 242 // Empty params resets the URL to be /search rather than /search?q= 243 // Also clear the tab parameter when soft resetting 244 const { ··· 265 ) 266 267 const onSearchInputFocus = useCallback(() => { 268 + if (IS_WEB) { 269 // Prevent a jump on iPad by ensuring that 270 // the initial focused render has no result list. 271 requestAnimationFrame(() => { ··· 282 283 // If a tab is specified, set the tab parameter 284 if (tab) { 285 + if (IS_WEB) { 286 navigation.setParams({...route.params, tab}) 287 } else { 288 navigation.setParams({tab}) ··· 299 <View 300 ref={headerRef} 301 onLayout={evt => { 302 + if (IS_WEB) setHeaderHeight(evt.nativeEvent.layout.height) 303 }} 304 style={[ 305 a.relative, ··· 581 } 582 583 function scrollToTopWeb() { 584 + if (IS_WEB) { 585 window.scrollTo(0, 0) 586 } 587 }
+2 -2
src/screens/Search/components/AutocompleteResults.tsx
··· 4 import {msg} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 - import {isNative} from '#/platform/detection' 8 import {useModerationOpts} from '#/state/preferences/moderation-opts' 9 import {SearchLinkCard} from '#/view/shell/desktop/Search' 10 import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard' 11 import {atoms as a, native, useTheme} from '#/alf' 12 import * as Layout from '#/components/Layout' 13 14 let AutocompleteResults = ({ 15 isAutocompleteFetching, ··· 46 label={_(msg`Search for "${searchText}"`)} 47 onPress={native(onSubmit)} 48 to={ 49 - isNative 50 ? undefined 51 : `/search?q=${encodeURIComponent(searchText)}` 52 }
··· 4 import {msg} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 import {useModerationOpts} from '#/state/preferences/moderation-opts' 8 import {SearchLinkCard} from '#/view/shell/desktop/Search' 9 import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard' 10 import {atoms as a, native, useTheme} from '#/alf' 11 import * as Layout from '#/components/Layout' 12 + import {IS_NATIVE} from '#/env' 13 14 let AutocompleteResults = ({ 15 isAutocompleteFetching, ··· 46 label={_(msg`Search for "${searchText}"`)} 47 onPress={native(onSubmit)} 48 to={ 49 + IS_NATIVE 50 ? undefined 51 : `/search?q=${encodeURIComponent(searchText)}` 52 }
+2 -2
src/screens/Search/modules/ExploreRecommendations.tsx
··· 3 import {Trans} from '@lingui/macro' 4 5 import {logger} from '#/logger' 6 - import {isWeb} from '#/platform/detection' 7 import { 8 DEFAULT_LIMIT as RECOMMENDATIONS_COUNT, 9 useTrendingTopics, ··· 17 TrendingTopicSkeleton, 18 } from '#/components/TrendingTopics' 19 import {Text} from '#/components/Typography' 20 21 // Note: This module is not currently used and may be removed in the future. 22 ··· 37 <View 38 style={[ 39 a.flex_row, 40 - isWeb 41 ? [a.px_lg, a.py_lg, a.pt_2xl, a.gap_md] 42 : [a.p_lg, a.pt_2xl, a.gap_md], 43 a.border_b,
··· 3 import {Trans} from '@lingui/macro' 4 5 import {logger} from '#/logger' 6 import { 7 DEFAULT_LIMIT as RECOMMENDATIONS_COUNT, 8 useTrendingTopics, ··· 16 TrendingTopicSkeleton, 17 } from '#/components/TrendingTopics' 18 import {Text} from '#/components/Typography' 19 + import {IS_WEB} from '#/env' 20 21 // Note: This module is not currently used and may be removed in the future. 22 ··· 37 <View 38 style={[ 39 a.flex_row, 40 + IS_WEB 41 ? [a.px_lg, a.py_lg, a.pt_2xl, a.gap_md] 42 : [a.p_lg, a.pt_2xl, a.gap_md], 43 a.border_b,
+4 -4
src/screens/Settings/AboutSettings.tsx
··· 9 10 import {STATUS_PAGE_URL} from '#/lib/constants' 11 import {type CommonNavigatorParams} from '#/lib/routes/types' 12 - import {isAndroid, isIOS, isNative} from '#/platform/detection' 13 import * as Toast from '#/view/com/util/Toast' 14 import * as SettingsList from '#/screens/Settings/components/SettingsList' 15 import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom' ··· 20 import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench' 21 import * as Layout from '#/components/Layout' 22 import {Loader} from '#/components/Loader' 23 import * as env from '#/env' 24 import {useDemoMode} from '#/storage/hooks/demo-mode' 25 import {useDevMode} from '#/storage/hooks/dev-mode' ··· 42 return spaceDiff * -1 43 }, 44 onSuccess: sizeDiffBytes => { 45 - if (isAndroid) { 46 Toast.show( 47 _( 48 msg({ ··· 108 <Trans>System log</Trans> 109 </SettingsList.ItemText> 110 </SettingsList.LinkItem> 111 - {isNative && ( 112 <SettingsList.PressableItem 113 onPress={() => onClearImageCache()} 114 label={_(msg`Clear image cache`)} ··· 157 {devModeEnabled && ( 158 <> 159 <OTAInfo /> 160 - {isIOS && ( 161 <SettingsList.PressableItem 162 onPress={() => { 163 const newDemoModeEnabled = !demoModeEnabled
··· 9 10 import {STATUS_PAGE_URL} from '#/lib/constants' 11 import {type CommonNavigatorParams} from '#/lib/routes/types' 12 import * as Toast from '#/view/com/util/Toast' 13 import * as SettingsList from '#/screens/Settings/components/SettingsList' 14 import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom' ··· 19 import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench' 20 import * as Layout from '#/components/Layout' 21 import {Loader} from '#/components/Loader' 22 + import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env' 23 import * as env from '#/env' 24 import {useDemoMode} from '#/storage/hooks/demo-mode' 25 import {useDevMode} from '#/storage/hooks/dev-mode' ··· 42 return spaceDiff * -1 43 }, 44 onSuccess: sizeDiffBytes => { 45 + if (IS_ANDROID) { 46 Toast.show( 47 _( 48 msg({ ··· 108 <Trans>System log</Trans> 109 </SettingsList.ItemText> 110 </SettingsList.LinkItem> 111 + {IS_NATIVE && ( 112 <SettingsList.PressableItem 113 onPress={() => onClearImageCache()} 114 label={_(msg`Clear image cache`)} ··· 157 {devModeEnabled && ( 158 <> 159 <OTAInfo /> 160 + {IS_IOS && ( 161 <SettingsList.PressableItem 162 onPress={() => { 163 const newDemoModeEnabled = !demoModeEnabled
+2 -2
src/screens/Settings/AccessibilitySettings.tsx
··· 3 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 4 5 import {type CommonNavigatorParams} from '#/lib/routes/types' 6 - import {isNative} from '#/platform/detection' 7 import { 8 useHapticsDisabled, 9 useRequireAltTextEnabled, ··· 20 import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' 21 import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic' 22 import * as Layout from '#/components/Layout' 23 24 type Props = NativeStackScreenProps< 25 CommonNavigatorParams, ··· 76 <Toggle.Platform /> 77 </Toggle.Item> 78 </SettingsList.Group> 79 - {isNative && ( 80 <> 81 <SettingsList.Divider /> 82 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
··· 3 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 4 5 import {type CommonNavigatorParams} from '#/lib/routes/types' 6 import { 7 useHapticsDisabled, 8 useRequireAltTextEnabled, ··· 19 import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' 20 import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic' 21 import * as Layout from '#/components/Layout' 22 + import {IS_NATIVE} from '#/env' 23 24 type Props = NativeStackScreenProps< 25 CommonNavigatorParams, ··· 76 <Toggle.Platform /> 77 </Toggle.Item> 78 </SettingsList.Group> 79 + {IS_NATIVE && ( 80 <> 81 <SettingsList.Divider /> 82 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
+3 -3
src/screens/Settings/AppIconSettings/index.tsx
··· 8 import {PressableScale} from '#/lib/custom-animations/PressableScale' 9 import {type CommonNavigatorParams} from '#/lib/routes/types' 10 import {useGate} from '#/lib/statsig/statsig' 11 - import {isAndroid} from '#/platform/detection' 12 import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' 13 import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' 14 import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' ··· 16 import * as Toggle from '#/components/forms/Toggle' 17 import * as Layout from '#/components/Layout' 18 import {Text} from '#/components/Typography' 19 import {IS_INTERNAL} from '#/env' 20 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'> ··· 29 ) 30 31 const onSetAppIcon = (icon: DynamicAppIcon.IconName) => { 32 - if (isAndroid) { 33 const next = 34 sets.defaults.find(i => i.id === icon) ?? 35 sets.core.find(i => i.id === icon) ··· 221 accessibilityHint={_(msg`Changes app icon`)} 222 targetScale={0.95} 223 onPress={() => { 224 - if (isAndroid) { 225 Alert.alert( 226 _(msg`Change app icon to "${icon.name}"`), 227 _(msg`The app will be restarted`),
··· 8 import {PressableScale} from '#/lib/custom-animations/PressableScale' 9 import {type CommonNavigatorParams} from '#/lib/routes/types' 10 import {useGate} from '#/lib/statsig/statsig' 11 import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' 12 import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' 13 import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' ··· 15 import * as Toggle from '#/components/forms/Toggle' 16 import * as Layout from '#/components/Layout' 17 import {Text} from '#/components/Typography' 18 + import {IS_ANDROID} from '#/env' 19 import {IS_INTERNAL} from '#/env' 20 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'> ··· 29 ) 30 31 const onSetAppIcon = (icon: DynamicAppIcon.IconName) => { 32 + if (IS_ANDROID) { 33 const next = 34 sets.defaults.find(i => i.id === icon) ?? 35 sets.core.find(i => i.id === icon) ··· 221 accessibilityHint={_(msg`Changes app icon`)} 222 targetScale={0.95} 223 onPress={() => { 224 + if (IS_ANDROID) { 225 Alert.alert( 226 _(msg`Change app icon to "${icon.name}"`), 227 _(msg`The app will be restarted`),
+52 -2
src/screens/Settings/AppearanceSettings.tsx
··· 13 type CommonNavigatorParams, 14 type NativeStackScreenProps, 15 } from '#/lib/routes/types' 16 - import {isNative} from '#/platform/detection' 17 import { 18 useEnableSquareAvatars, 19 useSetEnableSquareAvatars, ··· 39 import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase' 40 import * as Layout from '#/components/Layout' 41 import {Text} from '#/components/Typography' 42 import {IS_INTERNAL} from '#/env' 43 import * as SettingsList from './components/SettingsList' 44 ··· 308 </Toggle.Item> 309 </SettingsList.Group> 310 311 - {isNative && IS_INTERNAL && ( 312 <> 313 <SettingsList.Divider /> 314 <AppIconSettingsListItem />
··· 13 type CommonNavigatorParams, 14 type NativeStackScreenProps, 15 } from '#/lib/routes/types' 16 import { 17 useEnableSquareAvatars, 18 useSetEnableSquareAvatars, ··· 38 import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase' 39 import * as Layout from '#/components/Layout' 40 import {Text} from '#/components/Typography' 41 + import {IS_NATIVE} from '#/env' 42 import {IS_INTERNAL} from '#/env' 43 import * as SettingsList from './components/SettingsList' 44 ··· 308 </Toggle.Item> 309 </SettingsList.Group> 310 311 + <SettingsList.Divider /> 312 + 313 + <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 314 + <SettingsList.ItemIcon icon={SparkleIcon} /> 315 + <SettingsList.ItemText> 316 + <Trans>Logo</Trans> 317 + </SettingsList.ItemText> 318 + <Toggle.Item 319 + name="kawaii_mode" 320 + label={_(msg`Enable kawaii logo`)} 321 + value={kawaiiMode} 322 + onChange={value => setKawaiiMode(value)} 323 + style={[a.w_full]}> 324 + <Toggle.LabelText style={[a.flex_1]}> 325 + <Trans>Enable kawaii logo</Trans> 326 + </Toggle.LabelText> 327 + <Toggle.Platform /> 328 + </Toggle.Item> 329 + </SettingsList.Group> 330 + 331 + <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 332 + <SettingsList.ItemIcon icon={SquareIcon} /> 333 + <SettingsList.ItemText> 334 + <Trans>Shapes</Trans> 335 + </SettingsList.ItemText> 336 + <Toggle.Item 337 + name="enable_square_avatars" 338 + label={_(msg`Enable square avatars`)} 339 + value={enableSquareAvatars} 340 + onChange={value => setEnableSquareAvatars(value)} 341 + style={[a.w_full]}> 342 + <Toggle.LabelText style={[a.flex_1]}> 343 + <Trans>Enable square avatars</Trans> 344 + </Toggle.LabelText> 345 + <Toggle.Platform /> 346 + </Toggle.Item> 347 + 348 + <Toggle.Item 349 + name="enable_square_buttons" 350 + label={_(msg`Enable square buttons`)} 351 + value={enableSquareButtons} 352 + onChange={value => setEnableSquareButtons(value)} 353 + style={[a.w_full]}> 354 + <Toggle.LabelText style={[a.flex_1]}> 355 + <Trans>Enable square buttons</Trans> 356 + </Toggle.LabelText> 357 + <Toggle.Platform /> 358 + </Toggle.Item> 359 + </SettingsList.Group> 360 + 361 + {IS_NATIVE && IS_INTERNAL && ( 362 <> 363 <SettingsList.Divider /> 364 <AppIconSettingsListItem />
+2 -2
src/screens/Settings/ContentAndMediaSettings.tsx
··· 4 5 import {type CommonNavigatorParams} from '#/lib/routes/types' 6 import {logEvent} from '#/lib/statsig/statsig' 7 - import {isNative} from '#/platform/detection' 8 import {useAutoplayDisabled, useSetAutoplayDisabled} from '#/state/preferences' 9 import { 10 useInAppBrowser, ··· 26 import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending' 27 import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' 28 import * as Layout from '#/components/Layout' 29 import {LiveEventFeedsSettingsToggle} from '#/features/liveEvents/components/LiveEventFeedsSettingsToggle' 30 31 type Props = NativeStackScreenProps< ··· 97 </SettingsList.ItemText> 98 </SettingsList.LinkItem> 99 <SettingsList.Divider /> 100 - {isNative && ( 101 <Toggle.Item 102 name="use_in_app_browser" 103 label={_(msg`Use in-app browser to open links`)}
··· 4 5 import {type CommonNavigatorParams} from '#/lib/routes/types' 6 import {logEvent} from '#/lib/statsig/statsig' 7 import {useAutoplayDisabled, useSetAutoplayDisabled} from '#/state/preferences' 8 import { 9 useInAppBrowser, ··· 25 import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending' 26 import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' 27 import * as Layout from '#/components/Layout' 28 + import {IS_NATIVE} from '#/env' 29 import {LiveEventFeedsSettingsToggle} from '#/features/liveEvents/components/LiveEventFeedsSettingsToggle' 30 31 type Props = NativeStackScreenProps< ··· 97 </SettingsList.ItemText> 98 </SettingsList.LinkItem> 99 <SettingsList.Divider /> 100 + {IS_NATIVE && ( 101 <Toggle.Item 102 name="use_in_app_browser" 103 label={_(msg`Use in-app browser to open links`)}
+3 -3
src/screens/Settings/DeerSettings.tsx
··· 15 useDangerousSetGate, 16 useGatesCache, 17 } from '#/lib/statsig/statsig' 18 - import {isWeb} from '#/platform/detection' 19 import * as persisted from '#/state/persisted' 20 import {useGoLinksEnabled, useSetGoLinksEnabled} from '#/state/preferences' 21 import { ··· 133 import * as Layout from '#/components/Layout' 134 import {Text} from '#/components/Typography' 135 import {SearchProfileCard} from '../Search/components/SearchProfileCard' 136 type Props = NativeStackScreenProps<CommonNavigatorParams> 137 138 function ConstellationInstanceDialog({ ··· 188 )} 189 /> 190 191 - <View style={isWeb && [a.flex_row, a.justify_end]}> 192 <Button 193 label={_(msg`Save`)} 194 size="large" ··· 316 </Text> 317 )} 318 319 - <View style={isWeb && [a.flex_row, a.justify_end]}> 320 <Button 321 label={_(msg`Save`)} 322 size="large"
··· 15 useDangerousSetGate, 16 useGatesCache, 17 } from '#/lib/statsig/statsig' 18 import * as persisted from '#/state/persisted' 19 import {useGoLinksEnabled, useSetGoLinksEnabled} from '#/state/preferences' 20 import { ··· 132 import * as Layout from '#/components/Layout' 133 import {Text} from '#/components/Typography' 134 import {SearchProfileCard} from '../Search/components/SearchProfileCard' 135 + import { IS_WEB } from '#/env/index.web' 136 type Props = NativeStackScreenProps<CommonNavigatorParams> 137 138 function ConstellationInstanceDialog({ ··· 188 )} 189 /> 190 191 + <View style={IS_WEB && [a.flex_row, a.justify_end]}> 192 <Button 193 label={_(msg`Save`)} 194 size="large" ··· 316 </Text> 317 )} 318 319 + <View style={IS_WEB && [a.flex_row, a.justify_end]}> 320 <Button 321 label={_(msg`Save`)} 322 size="large"
+2 -2
src/screens/Settings/FindContactsSettings.tsx
··· 20 } from '#/lib/routes/types' 21 import {cleanError, isNetworkError} from '#/lib/strings/errors' 22 import {logger} from '#/logger' 23 - import {isNative} from '#/platform/detection' 24 import { 25 updateProfileShadow, 26 useProfileShadow, ··· 48 import * as ProfileCard from '#/components/ProfileCard' 49 import * as Toast from '#/components/Toast' 50 import {Text} from '#/components/Typography' 51 import type * as bsky from '#/types/bsky' 52 import {bulkWriteFollows} from '../Onboarding/util' 53 ··· 78 </Layout.Header.Content> 79 <Layout.Header.Slot /> 80 </Layout.Header.Outer> 81 - {isNative ? ( 82 data ? ( 83 !data.syncStatus ? ( 84 <Intro />
··· 20 } from '#/lib/routes/types' 21 import {cleanError, isNetworkError} from '#/lib/strings/errors' 22 import {logger} from '#/logger' 23 import { 24 updateProfileShadow, 25 useProfileShadow, ··· 47 import * as ProfileCard from '#/components/ProfileCard' 48 import * as Toast from '#/components/Toast' 49 import {Text} from '#/components/Typography' 50 + import {IS_NATIVE} from '#/env' 51 import type * as bsky from '#/types/bsky' 52 import {bulkWriteFollows} from '../Onboarding/util' 53 ··· 78 </Layout.Header.Content> 79 <Layout.Header.Slot /> 80 </Layout.Header.Outer> 81 + {IS_NATIVE ? ( 82 data ? ( 83 !data.syncStatus ? ( 84 <Intro />
+5 -5
src/screens/Settings/NotificationSettings/index.tsx
··· 11 type AllNavigatorParams, 12 type NativeStackScreenProps, 13 } from '#/lib/routes/types' 14 - import {isAndroid, isIOS, isWeb} from '#/platform/detection' 15 import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 16 import {atoms as a} from '#/alf' 17 import {Admonition} from '#/components/Admonition' ··· 31 } from '#/components/icons/Repost' 32 import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes' 33 import * as Layout from '#/components/Layout' 34 import * as SettingsList from '../components/SettingsList' 35 import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle' 36 ··· 45 const {data: permissions, refetch} = useQuery({ 46 queryKey: RQKEY, 47 queryFn: async () => { 48 - if (isWeb) return null 49 return await Notification.getPermissionsAsync() 50 }, 51 }) ··· 58 }, [appState, refetch]) 59 60 const onRequestPermissions = async () => { 61 - if (isWeb) return 62 if (permissions?.canAskAgain) { 63 const response = await Notification.requestPermissionsAsync() 64 queryClient.setQueryData(RQKEY, response) 65 } else { 66 - if (isAndroid) { 67 try { 68 await Linking.sendIntent( 69 'android.settings.APP_NOTIFICATION_SETTINGS', ··· 77 } catch { 78 Linking.openSettings() 79 } 80 - } else if (isIOS) { 81 Linking.openSettings() 82 } 83 }
··· 11 type AllNavigatorParams, 12 type NativeStackScreenProps, 13 } from '#/lib/routes/types' 14 import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 15 import {atoms as a} from '#/alf' 16 import {Admonition} from '#/components/Admonition' ··· 30 } from '#/components/icons/Repost' 31 import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes' 32 import * as Layout from '#/components/Layout' 33 + import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env' 34 import * as SettingsList from '../components/SettingsList' 35 import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle' 36 ··· 45 const {data: permissions, refetch} = useQuery({ 46 queryKey: RQKEY, 47 queryFn: async () => { 48 + if (IS_WEB) return null 49 return await Notification.getPermissionsAsync() 50 }, 51 }) ··· 58 }, [appState, refetch]) 59 60 const onRequestPermissions = async () => { 61 + if (IS_WEB) return 62 if (permissions?.canAskAgain) { 63 const response = await Notification.requestPermissionsAsync() 64 queryClient.setQueryData(RQKEY, response) 65 } else { 66 + if (IS_ANDROID) { 67 try { 68 await Linking.sendIntent( 69 'android.settings.APP_NOTIFICATION_SETTINGS', ··· 77 } catch { 78 Linking.openSettings() 79 } 80 + } else if (IS_IOS) { 81 Linking.openSettings() 82 } 83 }
+4 -4
src/screens/Settings/Settings.tsx
··· 19 import {useGate} from '#/lib/statsig/statsig' 20 import {sanitizeDisplayName} from '#/lib/strings/display-names' 21 import {sanitizeHandle} from '#/lib/strings/handles' 22 - import {isIOS, isNative} from '#/platform/detection' 23 import {useProfileShadow} from '#/state/cache/profile-shadow' 24 import * as persisted from '#/state/persisted' 25 import {clearStorage} from '#/state/persisted' ··· 73 shouldShowVerificationCheckButton, 74 VerificationCheckButton, 75 } from '#/components/verification/VerificationCheckButton' 76 import {IS_INTERNAL} from '#/env' 77 import {device, useStorage} from '#/storage' 78 import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' ··· 215 <Trans>Content and media</Trans> 216 </SettingsList.ItemText> 217 </SettingsList.LinkItem> 218 - {isNative && 219 findContactsEnabled && 220 !gate('disable_settings_find_contacts') && ( 221 <SettingsList.LinkItem ··· 518 <Trans>Clear all storage data (restart after this)</Trans> 519 </SettingsList.ItemText> 520 </SettingsList.PressableItem> 521 - {isIOS ? ( 522 <SettingsList.PressableItem 523 onPress={onPressApplyOta} 524 label={_(msg`Apply Pull Request`)}> ··· 527 </SettingsList.ItemText> 528 </SettingsList.PressableItem> 529 ) : null} 530 - {isNative && isCurrentlyRunningPullRequestDeployment ? ( 531 <SettingsList.PressableItem 532 onPress={revertToEmbedded} 533 label={_(msg`Unapply Pull Request`)}>
··· 19 import {useGate} from '#/lib/statsig/statsig' 20 import {sanitizeDisplayName} from '#/lib/strings/display-names' 21 import {sanitizeHandle} from '#/lib/strings/handles' 22 import {useProfileShadow} from '#/state/cache/profile-shadow' 23 import * as persisted from '#/state/persisted' 24 import {clearStorage} from '#/state/persisted' ··· 72 shouldShowVerificationCheckButton, 73 VerificationCheckButton, 74 } from '#/components/verification/VerificationCheckButton' 75 + import {IS_IOS, IS_NATIVE} from '#/env' 76 import {IS_INTERNAL} from '#/env' 77 import {device, useStorage} from '#/storage' 78 import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' ··· 215 <Trans>Content and media</Trans> 216 </SettingsList.ItemText> 217 </SettingsList.LinkItem> 218 + {IS_NATIVE && 219 findContactsEnabled && 220 !gate('disable_settings_find_contacts') && ( 221 <SettingsList.LinkItem ··· 518 <Trans>Clear all storage data (restart after this)</Trans> 519 </SettingsList.ItemText> 520 </SettingsList.PressableItem> 521 + {IS_IOS ? ( 522 <SettingsList.PressableItem 523 onPress={onPressApplyOta} 524 label={_(msg`Apply Pull Request`)}> ··· 527 </SettingsList.ItemText> 528 </SettingsList.PressableItem> 529 ) : null} 530 + {IS_NATIVE && isCurrentlyRunningPullRequestDeployment ? ( 531 <SettingsList.PressableItem 532 onPress={revertToEmbedded} 533 label={_(msg`Unapply Pull Request`)}>
+2 -2
src/screens/Settings/components/AddAppPasswordDialog.tsx
··· 13 import {useLingui} from '@lingui/react' 14 import {useMutation} from '@tanstack/react-query' 15 16 - import {isWeb} from '#/platform/detection' 17 import {useAppPasswordCreateMutation} from '#/state/queries/app-passwords' 18 import {atoms as a, native, useTheme} from '#/alf' 19 import {Admonition} from '#/components/Admonition' ··· 24 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 25 import {SquareBehindSquare4_Stroke2_Corner0_Rounded as CopyIcon} from '#/components/icons/SquareBehindSquare4' 26 import {Text} from '#/components/Typography' 27 import {CopyButton} from './CopyButton' 28 29 export function AddAppPasswordDialog({ ··· 181 ) : ( 182 <Animated.View 183 style={[a.gap_lg]} 184 - entering={isWeb ? FadeIn.delay(200) : SlideInRight} 185 key={1}> 186 <Text style={[a.text_2xl, a.font_semi_bold]}> 187 <Trans>Here is your app password!</Trans>
··· 13 import {useLingui} from '@lingui/react' 14 import {useMutation} from '@tanstack/react-query' 15 16 import {useAppPasswordCreateMutation} from '#/state/queries/app-passwords' 17 import {atoms as a, native, useTheme} from '#/alf' 18 import {Admonition} from '#/components/Admonition' ··· 23 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 24 import {SquareBehindSquare4_Stroke2_Corner0_Rounded as CopyIcon} from '#/components/icons/SquareBehindSquare4' 25 import {Text} from '#/components/Typography' 26 + import {IS_WEB} from '#/env' 27 import {CopyButton} from './CopyButton' 28 29 export function AddAppPasswordDialog({ ··· 181 ) : ( 182 <Animated.View 183 style={[a.gap_lg]} 184 + entering={IS_WEB ? FadeIn.delay(200) : SlideInRight} 185 key={1}> 186 <Text style={[a.text_2xl, a.font_semi_bold]}> 187 <Trans>Here is your app password!</Trans>
+2 -2
src/screens/Settings/components/ChangePasswordDialog.tsx
··· 7 import {cleanError, isNetworkError} from '#/lib/strings/errors' 8 import {checkAndFormatResetCode} from '#/lib/strings/password' 9 import {logger} from '#/logger' 10 - import {isNative} from '#/platform/detection' 11 import {useAgent, useSession} from '#/state/session' 12 import {pdsAgent} from '#/state/session/agent' 13 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' ··· 17 import * as TextField from '#/components/forms/TextField' 18 import {Loader} from '#/components/Loader' 19 import {Text} from '#/components/Typography' 20 21 enum Stages { 22 RequestCode = 'RequestCode', ··· 243 <Trans>Already have a code?</Trans> 244 </ButtonText> 245 </Button> 246 - {isNative && ( 247 <Button 248 label={_(msg`Cancel`)} 249 color="secondary"
··· 7 import {cleanError, isNetworkError} from '#/lib/strings/errors' 8 import {checkAndFormatResetCode} from '#/lib/strings/password' 9 import {logger} from '#/logger' 10 import {useAgent, useSession} from '#/state/session' 11 import {pdsAgent} from '#/state/session/agent' 12 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' ··· 16 import * as TextField from '#/components/forms/TextField' 17 import {Loader} from '#/components/Loader' 18 import {Text} from '#/components/Typography' 19 + import {IS_NATIVE} from '#/env' 20 21 enum Stages { 22 RequestCode = 'RequestCode', ··· 243 <Trans>Already have a code?</Trans> 244 </ButtonText> 245 </Button> 246 + {IS_NATIVE && ( 247 <Button 248 label={_(msg`Cancel`)} 249 color="secondary"
+2 -2
src/screens/Settings/components/DisableEmail2FADialog.tsx
··· 4 import {useLingui} from '@lingui/react' 5 6 import {cleanError} from '#/lib/strings/errors' 7 - import {isNative} from '#/platform/detection' 8 import {useAgent, useSession} from '#/state/session' 9 import {pdsAgent} from '#/state/session/agent' 10 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' ··· 16 import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' 17 import {Loader} from '#/components/Loader' 18 import {P, Text} from '#/components/Typography' 19 20 enum Stages { 21 Email, ··· 194 </View> 195 ) : undefined} 196 197 - {!gtMobile && isNative && <View style={{height: 40}} />} 198 </View> 199 </Dialog.ScrollableInner> 200 </Dialog.Outer>
··· 4 import {useLingui} from '@lingui/react' 5 6 import {cleanError} from '#/lib/strings/errors' 7 import {useAgent, useSession} from '#/state/session' 8 import {pdsAgent} from '#/state/session/agent' 9 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' ··· 15 import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' 16 import {Loader} from '#/components/Loader' 17 import {P, Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 20 enum Stages { 21 Email, ··· 194 </View> 195 ) : undefined} 196 197 + {!gtMobile && IS_NATIVE && <View style={{height: 40}} />} 198 </View> 199 </Dialog.ScrollableInner> 200 </Dialog.Outer>
+8 -6
src/screens/Signup/StepCaptcha/index.tsx
··· 7 8 import {createFullHandle} from '#/lib/strings/handles' 9 import {logger} from '#/logger' 10 - import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection' 11 import {useSignupContext} from '#/screens/Signup/state' 12 import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' 13 import {atoms as a, useTheme} from '#/alf' 14 import {FormError} from '#/components/forms/FormError' 15 import {GCP_PROJECT_ID} from '#/env' 16 import {BackNextButtons} from '../BackNextButtons' 17 18 const CAPTCHA_PATH = 19 - isWeb || GCP_PROJECT_ID === 0 ? '/gate/signup' : '/gate/signup/attempt-attest' 20 21 export function StepCaptcha() { 22 - if (isWeb) { 23 return <StepCaptchaInner /> 24 } else { 25 return <StepCaptchaNative /> ··· 35 ;(async () => { 36 logger.debug('trying to generate attestation token...') 37 try { 38 - if (isIOS) { 39 logger.debug('starting to generate devicecheck token...') 40 const token = await ReactNativeDeviceAttest.getDeviceCheckToken() 41 setToken(token) ··· 86 newUrl.searchParams.set('state', stateParam) 87 newUrl.searchParams.set('colorScheme', theme.name) 88 89 - if (isNative && token) { 90 newUrl.searchParams.set('platform', Platform.OS) 91 newUrl.searchParams.set('token', token) 92 - if (isAndroid && payload) { 93 newUrl.searchParams.set('payload', payload) 94 } 95 }
··· 7 8 import {createFullHandle} from '#/lib/strings/handles' 9 import {logger} from '#/logger' 10 import {useSignupContext} from '#/screens/Signup/state' 11 import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' 12 import {atoms as a, useTheme} from '#/alf' 13 import {FormError} from '#/components/forms/FormError' 14 + import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 15 import {GCP_PROJECT_ID} from '#/env' 16 import {BackNextButtons} from '../BackNextButtons' 17 18 const CAPTCHA_PATH = 19 + IS_WEB || GCP_PROJECT_ID === 0 20 + ? '/gate/signup' 21 + : '/gate/signup/attempt-attest' 22 23 export function StepCaptcha() { 24 + if (IS_WEB) { 25 return <StepCaptchaInner /> 26 } else { 27 return <StepCaptchaNative /> ··· 37 ;(async () => { 38 logger.debug('trying to generate attestation token...') 39 try { 40 + if (IS_IOS) { 41 logger.debug('starting to generate devicecheck token...') 42 const token = await ReactNativeDeviceAttest.getDeviceCheckToken() 43 setToken(token) ··· 88 newUrl.searchParams.set('state', stateParam) 89 newUrl.searchParams.set('colorScheme', theme.name) 90 91 + if (IS_NATIVE && token) { 92 newUrl.searchParams.set('platform', Platform.OS) 93 newUrl.searchParams.set('token', token) 94 + if (IS_ANDROID && payload) { 95 newUrl.searchParams.set('payload', payload) 96 } 97 }
+4 -4
src/screens/Signup/StepInfo/index.tsx
··· 8 import {DEFAULT_SERVICE} from '#/lib/constants' 9 import {isEmailMaybeInvalid} from '#/lib/strings/email' 10 import {logger} from '#/logger' 11 - import {isNative, isWeb} from '#/platform/detection' 12 import {useSignupContext} from '#/screens/Signup/state' 13 import {Policies} from '#/screens/Signup/StepInfo/Policies' 14 import {atoms as a, native} from '#/alf' ··· 37 MIN_ACCESS_AGE, 38 useAgeAssuranceRegionConfigWithFallback, 39 } from '#/ageAssurance/util' 40 import { 41 useDeviceGeolocationApi, 42 useIsDeviceGeolocationGranted, ··· 215 If you have one, sign in with an existing Bluesky account. 216 </Trans> 217 </Text> 218 - <View style={isWeb && [a.flex_row, a.justify_center]}> 219 <Button 220 testID="signInButton" 221 onPress={onPressSignIn} ··· 397 </Trans> 398 )} 399 </Admonition.Text> 400 - {isNative && 401 !isDeviceGeolocationGranted && 402 isOverAppMinAccessAge && ( 403 <Admonition.Text> ··· 429 ) : undefined} 430 </View> 431 432 - {isNative && ( 433 <DeviceLocationRequestDialog 434 control={locationControl} 435 onLocationAcquired={props => {
··· 8 import {DEFAULT_SERVICE} from '#/lib/constants' 9 import {isEmailMaybeInvalid} from '#/lib/strings/email' 10 import {logger} from '#/logger' 11 import {useSignupContext} from '#/screens/Signup/state' 12 import {Policies} from '#/screens/Signup/StepInfo/Policies' 13 import {atoms as a, native} from '#/alf' ··· 36 MIN_ACCESS_AGE, 37 useAgeAssuranceRegionConfigWithFallback, 38 } from '#/ageAssurance/util' 39 + import {IS_NATIVE, IS_WEB} from '#/env' 40 import { 41 useDeviceGeolocationApi, 42 useIsDeviceGeolocationGranted, ··· 215 If you have one, sign in with an existing Bluesky account. 216 </Trans> 217 </Text> 218 + <View style={IS_WEB && [a.flex_row, a.justify_center]}> 219 <Button 220 testID="signInButton" 221 onPress={onPressSignIn} ··· 397 </Trans> 398 )} 399 </Admonition.Text> 400 + {IS_NATIVE && 401 !isDeviceGeolocationGranted && 402 isOverAppMinAccessAge && ( 403 <Admonition.Text> ··· 429 ) : undefined} 430 </View> 431 432 + {IS_NATIVE && ( 433 <DeviceLocationRequestDialog 434 control={locationControl} 435 onLocationAcquired={props => {
+2 -2
src/screens/Signup/index.tsx
··· 8 9 import {FEEDBACK_FORM_URL} from '#/lib/constants' 10 import {logger} from '#/logger' 11 - import {isAndroid} from '#/platform/detection' 12 import {useServiceQuery} from '#/state/queries/service' 13 import {useStarterPackQuery} from '#/state/queries/starter-packs' 14 import {useActiveStarterPack} from '#/state/shell/starter-pack' ··· 30 import {InlineLinkText} from '#/components/Link' 31 import {ScreenTransition} from '#/components/ScreenTransition' 32 import {Text} from '#/components/Typography' 33 import {GCP_PROJECT_ID} from '#/env' 34 import * as bsky from '#/types/bsky' 35 ··· 114 115 // On Android, warmup the Play Integrity API on the signup screen so it is ready by the time we get to the gate screen. 116 useEffect(() => { 117 - if (!isAndroid) { 118 return 119 } 120 ReactNativeDeviceAttest.warmupIntegrity(GCP_PROJECT_ID).catch(err =>
··· 8 9 import {FEEDBACK_FORM_URL} from '#/lib/constants' 10 import {logger} from '#/logger' 11 import {useServiceQuery} from '#/state/queries/service' 12 import {useStarterPackQuery} from '#/state/queries/starter-packs' 13 import {useActiveStarterPack} from '#/state/shell/starter-pack' ··· 29 import {InlineLinkText} from '#/components/Link' 30 import {ScreenTransition} from '#/components/ScreenTransition' 31 import {Text} from '#/components/Typography' 32 + import {IS_ANDROID} from '#/env' 33 import {GCP_PROJECT_ID} from '#/env' 34 import * as bsky from '#/types/bsky' 35 ··· 114 115 // On Android, warmup the Play Integrity API on the signup screen so it is ready by the time we get to the gate screen. 116 useEffect(() => { 117 + if (!IS_ANDROID) { 118 return 119 } 120 ReactNativeDeviceAttest.warmupIntegrity(GCP_PROJECT_ID).catch(err =>
+3 -3
src/screens/SignupQueued.tsx
··· 6 import {useLingui} from '@lingui/react' 7 8 import {logger} from '#/logger' 9 - import {isIOS, isWeb} from '#/platform/detection' 10 import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' 11 import {pdsAgent} from '#/state/session/agent' 12 import {useOnboardingDispatch} from '#/state/shell' ··· 15 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 16 import {Loader} from '#/components/Loader' 17 import {P, Text} from '#/components/Typography' 18 19 const COL_WIDTH = 400 20 ··· 99 </Button> 100 ) 101 102 - const webLayout = isWeb && gtMobile 103 104 return ( 105 <Modal ··· 107 animationType={native('slide')} 108 presentationStyle="formSheet" 109 style={[web(a.util_screen_outer)]}> 110 - {isIOS && <SystemBars style={{statusBar: 'light'}} />} 111 <ScrollView 112 style={[a.flex_1, t.atoms.bg]} 113 contentContainerStyle={{borderWidth: 0}}
··· 6 import {useLingui} from '@lingui/react' 7 8 import {logger} from '#/logger' 9 import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' 10 import {pdsAgent} from '#/state/session/agent' 11 import {useOnboardingDispatch} from '#/state/shell' ··· 14 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 import {Loader} from '#/components/Loader' 16 import {P, Text} from '#/components/Typography' 17 + import {IS_IOS, IS_WEB} from '#/env' 18 19 const COL_WIDTH = 400 20 ··· 99 </Button> 100 ) 101 102 + const webLayout = IS_WEB && gtMobile 103 104 return ( 105 <Modal ··· 107 animationType={native('slide')} 108 presentationStyle="formSheet" 109 style={[web(a.util_screen_outer)]}> 110 + {IS_IOS && <SystemBars style={{statusBar: 'light'}} />} 111 <ScrollView 112 style={[a.flex_1, t.atoms.bg]} 113 contentContainerStyle={{borderWidth: 0}}
+3 -4
src/screens/StarterPack/StarterPackLandingScreen.tsx
··· 11 import {msg, Trans} from '@lingui/macro' 12 import {useLingui} from '@lingui/react' 13 14 - import {isAndroidWeb} from '#/lib/browser' 15 import {JOINED_THIS_WEEK} from '#/lib/constants' 16 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 17 import {logEvent} from '#/lib/statsig/statsig' 18 import {createStarterPackGooglePlayUri} from '#/lib/strings/starter-pack' 19 - import {isWeb} from '#/platform/detection' 20 import {useModerationOpts} from '#/state/preferences/moderation-opts' 21 import {useStarterPackQuery} from '#/state/queries/starter-packs' 22 import { ··· 38 import * as Prompt from '#/components/Prompt' 39 import {RichText} from '#/components/RichText' 40 import {Text} from '#/components/Typography' 41 import * as bsky from '#/types/bsky' 42 43 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) ··· 142 postAppClipMessage({ 143 action: 'present', 144 }) 145 - } else if (isAndroidWeb) { 146 androidDialogControl.open() 147 } else { 148 onContinue() ··· 359 /> 360 </Prompt.Actions> 361 </Prompt.Outer> 362 - {isWeb && ( 363 <meta 364 name="apple-itunes-app" 365 content="app-id=app.witchsky, app-clip-bundle-id=app.witchsky.AppClip, app-clip-display=card"
··· 11 import {msg, Trans} from '@lingui/macro' 12 import {useLingui} from '@lingui/react' 13 14 import {JOINED_THIS_WEEK} from '#/lib/constants' 15 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 16 import {logEvent} from '#/lib/statsig/statsig' 17 import {createStarterPackGooglePlayUri} from '#/lib/strings/starter-pack' 18 import {useModerationOpts} from '#/state/preferences/moderation-opts' 19 import {useStarterPackQuery} from '#/state/queries/starter-packs' 20 import { ··· 36 import * as Prompt from '#/components/Prompt' 37 import {RichText} from '#/components/RichText' 38 import {Text} from '#/components/Typography' 39 + import {IS_WEB, IS_WEB_MOBILE_ANDROID} from '#/env' 40 import * as bsky from '#/types/bsky' 41 42 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) ··· 141 postAppClipMessage({ 142 action: 'present', 143 }) 144 + } else if (IS_WEB_MOBILE_ANDROID) { 145 androidDialogControl.open() 146 } else { 147 onContinue() ··· 358 /> 359 </Prompt.Actions> 360 </Prompt.Outer> 361 + {IS_WEB && ( 362 <meta 363 name="apple-itunes-app" 364 content="app-id=app.witchsky, app-clip-bundle-id=app.witchsky.AppClip, app-clip-display=card"
+4 -4
src/screens/StarterPack/StarterPackScreen.tsx
··· 27 import {cleanError} from '#/lib/strings/errors' 28 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 29 import {logger} from '#/logger' 30 - import {isWeb} from '#/platform/detection' 31 import {updateProfileShadow} from '#/state/cache/profile-shadow' 32 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 33 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 73 import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' 74 import {ShareDialog} from '#/components/StarterPack/ShareDialog' 75 import {Text} from '#/components/Typography' 76 import * as bsky from '#/types/bsky' 77 78 type StarterPackScreeProps = NativeStackScreenProps< ··· 611 <Menu.Group> 612 <Menu.Item 613 label={ 614 - isWeb 615 ? _(msg`Copy link to starter pack`) 616 : _(msg`Share via...`) 617 } 618 testID="shareStarterPackLinkBtn" 619 onPress={onOpenShareDialog}> 620 <Menu.ItemText> 621 - {isWeb ? ( 622 <Trans>Copy link</Trans> 623 ) : ( 624 <Trans>Share via...</Trans> 625 )} 626 </Menu.ItemText> 627 <Menu.ItemIcon 628 - icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} 629 position="right" 630 /> 631 </Menu.Item>
··· 27 import {cleanError} from '#/lib/strings/errors' 28 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 29 import {logger} from '#/logger' 30 import {updateProfileShadow} from '#/state/cache/profile-shadow' 31 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 32 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 72 import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' 73 import {ShareDialog} from '#/components/StarterPack/ShareDialog' 74 import {Text} from '#/components/Typography' 75 + import {IS_WEB} from '#/env' 76 import * as bsky from '#/types/bsky' 77 78 type StarterPackScreeProps = NativeStackScreenProps< ··· 611 <Menu.Group> 612 <Menu.Item 613 label={ 614 + IS_WEB 615 ? _(msg`Copy link to starter pack`) 616 : _(msg`Share via...`) 617 } 618 testID="shareStarterPackLinkBtn" 619 onPress={onOpenShareDialog}> 620 <Menu.ItemText> 621 + {IS_WEB ? ( 622 <Trans>Copy link</Trans> 623 ) : ( 624 <Trans>Share via...</Trans> 625 )} 626 </Menu.ItemText> 627 <Menu.ItemIcon 628 + icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon} 629 position="right" 630 /> 631 </Menu.Item>
+2 -2
src/screens/StarterPack/Wizard/StepProfiles.tsx
··· 4 import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api' 5 import {Trans} from '@lingui/macro' 6 7 - import {isNative} from '#/platform/detection' 8 import {useA11y} from '#/state/a11y' 9 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 10 import {useActorSearch} from '#/state/queries/actor-search' ··· 16 import {ScreenTransition} from '#/components/ScreenTransition' 17 import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' 18 import {Text} from '#/components/Typography' 19 import type * as bsky from '#/types/bsky' 20 21 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { ··· 89 onEndReached={ 90 !query && !screenReaderEnabled ? () => fetchNextPage() : undefined 91 } 92 - onEndReachedThreshold={isNative ? 2 : 0.25} 93 keyboardDismissMode="on-drag" 94 ListEmptyComponent={ 95 <View style={[a.flex_1, a.align_center, a.mt_lg, a.px_lg]}>
··· 4 import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api' 5 import {Trans} from '@lingui/macro' 6 7 import {useA11y} from '#/state/a11y' 8 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 9 import {useActorSearch} from '#/state/queries/actor-search' ··· 15 import {ScreenTransition} from '#/components/ScreenTransition' 16 import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' 17 import {Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 import type * as bsky from '#/types/bsky' 20 21 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { ··· 89 onEndReached={ 90 !query && !screenReaderEnabled ? () => fetchNextPage() : undefined 91 } 92 + onEndReachedThreshold={IS_NATIVE ? 2 : 0.25} 93 keyboardDismissMode="on-drag" 94 ListEmptyComponent={ 95 <View style={[a.flex_1, a.align_center, a.mt_lg, a.px_lg]}>
+3 -3
src/screens/StarterPack/Wizard/index.tsx
··· 31 parseStarterPackUri, 32 } from '#/lib/strings/starter-pack' 33 import {logger} from '#/logger' 34 - import {isNative} from '#/platform/detection' 35 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 36 import {useModerationOpts} from '#/state/preferences/moderation-opts' 37 import {useAllListMembersQuery} from '#/state/queries/list-members' ··· 60 import {Loader} from '#/components/Loader' 61 import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog' 62 import {Text} from '#/components/Typography' 63 import type * as bsky from '#/types/bsky' 64 import {Provider} from './State' 65 ··· 437 { 438 paddingBottom: a.pb_lg.paddingBottom + bottomInset, 439 }, 440 - isNative && [ 441 a.border_l, 442 a.border_r, 443 t.atoms.shadow_md, ··· 603 a.w_full, 604 a.align_center, 605 a.gap_2xl, 606 - isNative ? a.mt_sm : a.mt_md, 607 ]}> 608 {state.currentStep === 'Profiles' && items.length < 8 && ( 609 <Text
··· 31 parseStarterPackUri, 32 } from '#/lib/strings/starter-pack' 33 import {logger} from '#/logger' 34 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 35 import {useModerationOpts} from '#/state/preferences/moderation-opts' 36 import {useAllListMembersQuery} from '#/state/queries/list-members' ··· 59 import {Loader} from '#/components/Loader' 60 import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog' 61 import {Text} from '#/components/Typography' 62 + import {IS_NATIVE} from '#/env' 63 import type * as bsky from '#/types/bsky' 64 import {Provider} from './State' 65 ··· 437 { 438 paddingBottom: a.pb_lg.paddingBottom + bottomInset, 439 }, 440 + IS_NATIVE && [ 441 a.border_l, 442 a.border_r, 443 t.atoms.shadow_md, ··· 603 a.w_full, 604 a.align_center, 605 a.gap_2xl, 606 + IS_NATIVE ? a.mt_sm : a.mt_md, 607 ]}> 608 {state.currentStep === 'Profiles' && items.length < 8 && ( 609 <Text
+2 -2
src/screens/Takendown.tsx
··· 14 } from '#/lib/constants' 15 import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 16 import {cleanError} from '#/lib/strings/errors' 17 - import {isWeb} from '#/platform/detection' 18 import {useAgent, useSession, useSessionApi} from '#/state/session' 19 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' 20 import {Logo} from '#/view/icons/Logo' ··· 24 import {SimpleInlineLinkText} from '#/components/Link' 25 import {Loader} from '#/components/Loader' 26 import {P, Text} from '#/components/Typography' 27 28 const COL_WIDTH = 400 29 ··· 119 </Button> 120 ) 121 122 - const webLayout = isWeb && gtMobile 123 124 useEnableKeyboardController(true) 125
··· 14 } from '#/lib/constants' 15 import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 16 import {cleanError} from '#/lib/strings/errors' 17 import {useAgent, useSession, useSessionApi} from '#/state/session' 18 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' 19 import {Logo} from '#/view/icons/Logo' ··· 23 import {SimpleInlineLinkText} from '#/components/Link' 24 import {Loader} from '#/components/Loader' 25 import {P, Text} from '#/components/Typography' 26 + import {IS_WEB} from '#/env' 27 28 const COL_WIDTH = 400 29 ··· 119 </Button> 120 ) 121 122 + const webLayout = IS_WEB && gtMobile 123 124 useEnableKeyboardController(true) 125
+4 -4
src/screens/VideoFeed/index.tsx
··· 57 import {cleanError} from '#/lib/strings/errors' 58 import {sanitizeHandle} from '#/lib/strings/handles' 59 import {logger} from '#/logger' 60 - import {isAndroid} from '#/platform/detection' 61 import {useA11y} from '#/state/a11y' 62 import { 63 POST_TOMBSTONE, ··· 102 import {PostControls} from '#/components/PostControls' 103 import {RichText} from '#/components/RichText' 104 import {Text} from '#/components/Typography' 105 import * as bsky from '#/types/bsky' 106 import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber' 107 ··· 591 embed: AppBskyEmbedVideo.View 592 }) { 593 const {bottom} = useSafeAreaInsets() 594 - const [isReady, setIsReady] = useState(!isAndroid) 595 596 useEventListener(player, 'timeUpdate', evt => { 597 - if (isAndroid && !isReady && evt.currentTime >= 0.05) { 598 setIsReady(true) 599 } 600 }) ··· 921 </LinearGradient> 922 </View> 923 {/* 924 - {isAndroid && status === 'loading' && ( 925 <View 926 style={[ 927 a.absolute,
··· 57 import {cleanError} from '#/lib/strings/errors' 58 import {sanitizeHandle} from '#/lib/strings/handles' 59 import {logger} from '#/logger' 60 import {useA11y} from '#/state/a11y' 61 import { 62 POST_TOMBSTONE, ··· 101 import {PostControls} from '#/components/PostControls' 102 import {RichText} from '#/components/RichText' 103 import {Text} from '#/components/Typography' 104 + import {IS_ANDROID} from '#/env' 105 import * as bsky from '#/types/bsky' 106 import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber' 107 ··· 591 embed: AppBskyEmbedVideo.View 592 }) { 593 const {bottom} = useSafeAreaInsets() 594 + const [isReady, setIsReady] = useState(!IS_ANDROID) 595 596 useEventListener(player, 'timeUpdate', evt => { 597 + if (IS_ANDROID && !isReady && evt.currentTime >= 0.05) { 598 setIsReady(true) 599 } 600 }) ··· 921 </LinearGradient> 922 </View> 923 {/* 924 + {IS_ANDROID && status === 'loading' && ( 925 <View 926 style={[ 927 a.absolute,
+2 -2
src/state/a11y.tsx
··· 1 import React from 'react' 2 import {AccessibilityInfo} from 'react-native' 3 4 - import {isWeb} from '#/platform/detection' 5 import {PlatformInfo} from '../../modules/expo-bluesky-swiss-army' 6 7 const Context = React.createContext({ ··· 58 * 59 * @see https://github.com/necolas/react-native-web/discussions/2072 60 */ 61 - screenReaderEnabled: isWeb ? false : screenReaderEnabled, 62 } 63 }, [reduceMotionEnabled, screenReaderEnabled]) 64
··· 1 import React from 'react' 2 import {AccessibilityInfo} from 'react-native' 3 4 + import {IS_WEB} from '#/env' 5 import {PlatformInfo} from '../../modules/expo-bluesky-swiss-army' 6 7 const Context = React.createContext({ ··· 58 * 59 * @see https://github.com/necolas/react-native-web/discussions/2072 60 */ 61 + screenReaderEnabled: IS_WEB ? false : screenReaderEnabled, 62 } 63 }, [reduceMotionEnabled, screenReaderEnabled]) 64
+2 -2
src/state/dialogs/index.tsx
··· 1 import React from 'react' 2 3 - import {isWeb} from '#/platform/detection' 4 import {type DialogControlRefProps} from '#/components/Dialog' 5 import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context' 6 import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet' 7 8 interface IDialogContext { ··· 62 const openDialogs = React.useRef<Set<string>>(new Set()) 63 64 const closeAllDialogs = React.useCallback(() => { 65 - if (isWeb) { 66 openDialogs.current.forEach(id => { 67 const dialog = activeDialogs.current.get(id) 68 if (dialog) dialog.current.close()
··· 1 import React from 'react' 2 3 import {type DialogControlRefProps} from '#/components/Dialog' 4 import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context' 5 + import {IS_WEB} from '#/env' 6 import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet' 7 8 interface IDialogContext { ··· 62 const openDialogs = React.useRef<Set<string>>(new Set()) 63 64 const closeAllDialogs = React.useCallback(() => { 65 + if (IS_WEB) { 66 openDialogs.current.forEach(id => { 67 const dialog = activeDialogs.current.get(id) 68 if (dialog) dialog.current.close()
+5 -5
src/state/gallery.ts
··· 19 import {type PickerImage} from '#/lib/media/picker.shared' 20 import {getDataUriSize} from '#/lib/media/util' 21 import {isCancelledError} from '#/lib/strings/errors' 22 - import {isNative} from '#/platform/detection' 23 24 export type ImageTransformation = { 25 crop?: ActionCrop['crop'] ··· 57 let _imageCacheDirectory: string 58 59 function getImageCacheDirectory(): string | null { 60 - if (isNative) { 61 return (_imageCacheDirectory ??= joinPath(cacheDirectory!, 'bsky-composer')) 62 } 63 ··· 124 } 125 126 export async function cropImage(img: ComposerImage): Promise<ComposerImage> { 127 - if (!isNative) { 128 return img 129 } 130 ··· 247 } 248 249 async function moveIfNecessary(from: string) { 250 - const cacheDir = isNative && getImageCacheDirectory() 251 252 if (cacheDir && from.startsWith(cacheDir)) { 253 const to = joinPath(cacheDir, nanoid(36)) ··· 263 264 /** Purge files that were created to accomodate image manipulation */ 265 export async function purgeTemporaryImageFiles() { 266 - const cacheDir = isNative && getImageCacheDirectory() 267 268 if (cacheDir) { 269 await deleteAsync(cacheDir, {idempotent: true})
··· 19 import {type PickerImage} from '#/lib/media/picker.shared' 20 import {getDataUriSize} from '#/lib/media/util' 21 import {isCancelledError} from '#/lib/strings/errors' 22 + import {IS_NATIVE} from '#/env' 23 24 export type ImageTransformation = { 25 crop?: ActionCrop['crop'] ··· 57 let _imageCacheDirectory: string 58 59 function getImageCacheDirectory(): string | null { 60 + if (IS_NATIVE) { 61 return (_imageCacheDirectory ??= joinPath(cacheDirectory!, 'bsky-composer')) 62 } 63 ··· 124 } 125 126 export async function cropImage(img: ComposerImage): Promise<ComposerImage> { 127 + if (!IS_NATIVE) { 128 return img 129 } 130 ··· 247 } 248 249 async function moveIfNecessary(from: string) { 250 + const cacheDir = IS_NATIVE && getImageCacheDirectory() 251 252 if (cacheDir && from.startsWith(cacheDir)) { 253 const to = joinPath(cacheDir, nanoid(36)) ··· 263 264 /** Purge files that were created to accomodate image manipulation */ 265 export async function purgeTemporaryImageFiles() { 266 + const cacheDir = IS_NATIVE && getImageCacheDirectory() 267 268 if (cacheDir) { 269 await deleteAsync(cacheDir, {idempotent: true})
+3 -3
src/state/messages/convo/agent.ts
··· 16 isNetworkError, 17 } from '#/lib/strings/errors' 18 import {Logger} from '#/logger' 19 - import {isNative} from '#/platform/detection' 20 import { 21 ACTIVE_POLL_INTERVAL, 22 BACKGROUND_POLL_INTERVAL, ··· 37 } from '#/state/messages/convo/types' 38 import {type MessagesEventBus} from '#/state/messages/events/agent' 39 import {type MessagesEventBusError} from '#/state/messages/events/types' 40 41 const logger = Logger.create(Logger.Context.ConversationAgent) 42 ··· 95 this.convoId = params.convoId 96 this.agent = params.agent 97 this.events = params.events 98 - this.senderUserDid = params.agent.session?.did! 99 100 if (params.placeholderData) { 101 this.setupPlaceholderData(params.placeholderData) ··· 639 { 640 cursor: nextCursor, 641 convoId: this.convoId, 642 - limit: isNative ? 30 : 60, 643 }, 644 {headers: DM_SERVICE_HEADERS}, 645 )
··· 16 isNetworkError, 17 } from '#/lib/strings/errors' 18 import {Logger} from '#/logger' 19 import { 20 ACTIVE_POLL_INTERVAL, 21 BACKGROUND_POLL_INTERVAL, ··· 36 } from '#/state/messages/convo/types' 37 import {type MessagesEventBus} from '#/state/messages/events/agent' 38 import {type MessagesEventBusError} from '#/state/messages/events/types' 39 + import {IS_NATIVE} from '#/env' 40 41 const logger = Logger.create(Logger.Context.ConversationAgent) 42 ··· 95 this.convoId = params.convoId 96 this.agent = params.agent 97 this.events = params.events 98 + this.senderUserDid = params.agent.assertDid 99 100 if (params.placeholderData) { 101 this.setupPlaceholderData(params.placeholderData) ··· 639 { 640 cursor: nextCursor, 641 convoId: this.convoId, 642 + limit: IS_NATIVE ? 30 : 60, 643 }, 644 {headers: DM_SERVICE_HEADERS}, 645 )
+5
src/state/persisted/index.web.ts
··· 22 let _state: Schema = defaults 23 const _emitter = new EventEmitter() 24 25 export async function init() { 26 broadcast.onmessage = onBroadcastMessage 27 window.onstorage = onStorage ··· 37 } 38 get satisfies PersistedApi['get'] 39 40 export async function write<K extends keyof Schema>( 41 key: K, 42 value: Schema[K], ··· 82 } 83 onUpdate satisfies PersistedApi['onUpdate'] 84 85 export async function clearStorage() { 86 try { 87 localStorage.removeItem(BSKY_STORAGE) ··· 102 } 103 } 104 105 async function onBroadcastMessage({data}: MessageEvent) { 106 if ( 107 typeof data === 'object' &&
··· 22 let _state: Schema = defaults 23 const _emitter = new EventEmitter() 24 25 + // async, to match native implementation 26 + // eslint-disable-next-line @typescript-eslint/require-await 27 export async function init() { 28 broadcast.onmessage = onBroadcastMessage 29 window.onstorage = onStorage ··· 39 } 40 get satisfies PersistedApi['get'] 41 42 + // eslint-disable-next-line @typescript-eslint/require-await 43 export async function write<K extends keyof Schema>( 44 key: K, 45 value: Schema[K], ··· 85 } 86 onUpdate satisfies PersistedApi['onUpdate'] 87 88 + // eslint-disable-next-line @typescript-eslint/require-await 89 export async function clearStorage() { 90 try { 91 localStorage.removeItem(BSKY_STORAGE) ··· 106 } 107 } 108 109 + // eslint-disable-next-line @typescript-eslint/require-await 110 async function onBroadcastMessage({data}: MessageEvent) { 111 if ( 112 typeof data === 'object' &&
+1 -1
src/state/preferences/index.tsx
··· 51 useSetExternalEmbedPref, 52 } from './external-embeds-prefs' 53 export {useGoLinksEnabled, useSetGoLinksEnabled} from './go-links-enabled' 54 - export * from './hidden-posts' 55 export { 56 useHideFeedsPromoTab, 57 useSetHideFeedsPromoTab,
··· 51 useSetExternalEmbedPref, 52 } from './external-embeds-prefs' 53 export {useGoLinksEnabled, useSetGoLinksEnabled} from './go-links-enabled' 54 + export {useHiddenPosts, useHiddenPostsApi} from './hidden-posts' 55 export { 56 useHideFeedsPromoTab, 57 useSetHideFeedsPromoTab,
+2 -2
src/state/preferences/kawaii.tsx
··· 1 import React from 'react' 2 3 - import {isWeb} from '#/platform/detection' 4 import * as persisted from '#/state/persisted' 5 6 type StateContext = persisted.Schema['kawaii'] 7 ··· 30 React.useEffect(() => { 31 // dumb and stupid but it's web only so just refresh the page if you want to change it 32 33 - if (isWeb) { 34 const kawaii = new URLSearchParams(window.location.search).get('kawaii') 35 switch (kawaii) { 36 case 'true':
··· 1 import React from 'react' 2 3 import * as persisted from '#/state/persisted' 4 + import {IS_WEB} from '#/env' 5 6 type StateContext = persisted.Schema['kawaii'] 7 ··· 30 React.useEffect(() => { 31 // dumb and stupid but it's web only so just refresh the page if you want to change it 32 33 + if (IS_WEB) { 34 const kawaii = new URLSearchParams(window.location.search).get('kawaii') 35 switch (kawaii) { 36 case 'true':
+7 -1
src/state/queries/preferences/types.ts
··· 15 } 16 17 export type ThreadViewPreferences = { 18 - sort: 'hotness' | 'oldest' | 'newest' | 'most-likes' | 'random' | string 19 lab_treeViewEnabled?: boolean 20 }
··· 15 } 16 17 export type ThreadViewPreferences = { 18 + sort: 19 + | 'hotness' 20 + | 'oldest' 21 + | 'newest' 22 + | 'most-likes' 23 + | 'random' 24 + | (string & {}) 25 lab_treeViewEnabled?: boolean 26 }
+2 -2
src/state/queries/usePostThread/index.ts
··· 1 import {useCallback, useMemo, useState} from 'react' 2 import {useQuery, useQueryClient} from '@tanstack/react-query' 3 4 - import {isWeb} from '#/platform/detection' 5 import {useModerationOpts} from '#/state/preferences/moderation-opts' 6 import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences' 7 import { ··· 31 import {useAgent, useSession} from '#/state/session' 32 import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' 33 import {useBreakpoints} from '#/alf' 34 35 export * from '#/state/queries/usePostThread/context' 36 export {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread/queryCache' ··· 53 const below = useMemo(() => { 54 return view === 'linear' 55 ? LINEAR_VIEW_BELOW 56 - : isWeb && gtPhone 57 ? TREE_VIEW_BELOW_DESKTOP 58 : TREE_VIEW_BELOW 59 }, [view, gtPhone])
··· 1 import {useCallback, useMemo, useState} from 'react' 2 import {useQuery, useQueryClient} from '@tanstack/react-query' 3 4 import {useModerationOpts} from '#/state/preferences/moderation-opts' 5 import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences' 6 import { ··· 30 import {useAgent, useSession} from '#/state/session' 31 import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' 32 import {useBreakpoints} from '#/alf' 33 + import {IS_WEB} from '#/env' 34 35 export * from '#/state/queries/usePostThread/context' 36 export {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread/queryCache' ··· 53 const below = useMemo(() => { 54 return view === 'linear' 55 ? LINEAR_VIEW_BELOW 56 + : IS_WEB && gtPhone 57 ? TREE_VIEW_BELOW_DESKTOP 58 : TREE_VIEW_BELOW 59 }, [view, gtPhone])
-1
src/state/queries/usePostThread/traversal.ts
··· 1 - /* eslint-disable no-labels */ 2 import {AppBskyUnspeccedDefs, type ModerationOpts} from '@atproto/api' 3 4 import {
··· 1 import {AppBskyUnspeccedDefs, type ModerationOpts} from '@atproto/api' 2 3 import {
+6 -2
src/state/service-config.tsx
··· 85 return useContext(TrendingContext) 86 } 87 88 - const DEFAULT_LIVE_ALLOWED_DOMAINS = ['twitch.tv', 'www.twitch.tv'] 89 export type LiveNowConfig = { 90 allowedDomains: Set<string> 91 } ··· 106 const gate = useGate() 107 const {hasSession} = useSession() 108 if (!hasSession) return false 109 - return IS_DEV ? true : gate('live_now_beta') 110 } 111 112 export function useCheckEmailConfirmed() {
··· 85 return useContext(TrendingContext) 86 } 87 88 + const DEFAULT_LIVE_ALLOWED_DOMAINS = [ 89 + 'twitch.tv', 90 + 'www.twitch.tv', 91 + 'stream.place', 92 + ] 93 export type LiveNowConfig = { 94 allowedDomains: Set<string> 95 } ··· 110 const gate = useGate() 111 const {hasSession} = useSession() 112 if (!hasSession) return false 113 + return IS_DEV ? true : !gate('disable_live_now_beta') 114 } 115 116 export function useCheckEmailConfirmed() {
+2 -2
src/state/session/index.tsx
··· 1 import React, {useMemo} from 'react' 2 import {type AtpSessionEvent, type BskyAgent} from '@atproto/api' 3 4 - import {isWeb} from '#/platform/detection' 5 import * as persisted from '#/state/persisted' 6 import {useCloseAllActiveElements} from '#/state/util' 7 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 8 import {emitSessionDropped} from '../events' 9 import { 10 agentToSessionAccount, ··· 341 ) 342 343 // @ts-expect-error window type is not declared, debug only 344 - if (__DEV__ && isWeb) window.agent = state.currentAgentState.agent 345 346 const agent = state.currentAgentState.agent as BskyAppAgent 347 const currentAgentRef = React.useRef(agent)
··· 1 import React, {useMemo} from 'react' 2 import {type AtpSessionEvent, type BskyAgent} from '@atproto/api' 3 4 import * as persisted from '#/state/persisted' 5 import {useCloseAllActiveElements} from '#/state/util' 6 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 7 + import {IS_WEB} from '#/env' 8 import {emitSessionDropped} from '../events' 9 import { 10 agentToSessionAccount, ··· 341 ) 342 343 // @ts-expect-error window type is not declared, debug only 344 + if (__DEV__ && IS_WEB) window.agent = state.currentAgentState.agent 345 346 const agent = state.currentAgentState.agent as BskyAppAgent 347 const currentAgentRef = React.useRef(agent)
+3 -3
src/state/shell/logged-out.tsx
··· 1 import React from 'react' 2 3 - import {isWeb} from '#/platform/detection' 4 import {useSession} from '#/state/session' 5 import {useActiveStarterPack} from '#/state/shell/starter-pack' 6 7 type State = { 8 showLoggedOut: boolean ··· 26 /** 27 * The did of the account to populate the login form with. 28 */ 29 - requestedAccount?: string | 'none' | 'new' | 'starterpack' 30 }) => void 31 /** 32 * Clears the requested account so that next time the logged out view is ··· 55 const [state, setState] = React.useState<State>({ 56 showLoggedOut: shouldShowStarterPack, 57 requestedAccountSwitchTo: shouldShowStarterPack 58 - ? isWeb 59 ? 'starterpack' 60 : 'new' 61 : undefined,
··· 1 import React from 'react' 2 3 import {useSession} from '#/state/session' 4 import {useActiveStarterPack} from '#/state/shell/starter-pack' 5 + import {IS_WEB} from '#/env' 6 7 type State = { 8 showLoggedOut: boolean ··· 26 /** 27 * The did of the account to populate the login form with. 28 */ 29 + requestedAccount?: (string & {}) | 'none' | 'new' | 'starterpack' 30 }) => void 31 /** 32 * Clears the requested account so that next time the logged out view is ··· 55 const [state, setState] = React.useState<State>({ 56 showLoggedOut: shouldShowStarterPack, 57 requestedAccountSwitchTo: shouldShowStarterPack 58 + ? IS_WEB 59 ? 'starterpack' 60 : 'new' 61 : undefined,
+3 -3
src/state/shell/selected-feed.tsx
··· 1 import {createContext, useCallback, useContext, useState} from 'react' 2 3 - import {isWeb} from '#/platform/detection' 4 import {type FeedDescriptor} from '#/state/queries/post-feed' 5 import {useSession} from '#/state/session' 6 import {account} from '#/storage' 7 8 type StateContext = FeedDescriptor | null ··· 14 setContext.displayName = 'SelectedFeedSetContext' 15 16 function getInitialFeed(did?: string): FeedDescriptor | null { 17 - if (isWeb) { 18 if (window.location.pathname === '/') { 19 const params = new URLSearchParams(window.location.search) 20 const feedFromUrl = params.get('feed') ··· 49 const saveState = useCallback( 50 (feed: FeedDescriptor) => { 51 setState(feed) 52 - if (isWeb) { 53 try { 54 sessionStorage.setItem('lastSelectedHomeFeed', feed) 55 } catch {}
··· 1 import {createContext, useCallback, useContext, useState} from 'react' 2 3 import {type FeedDescriptor} from '#/state/queries/post-feed' 4 import {useSession} from '#/state/session' 5 + import {IS_WEB} from '#/env' 6 import {account} from '#/storage' 7 8 type StateContext = FeedDescriptor | null ··· 14 setContext.displayName = 'SelectedFeedSetContext' 15 16 function getInitialFeed(did?: string): FeedDescriptor | null { 17 + if (IS_WEB) { 18 if (window.location.pathname === '/') { 19 const params = new URLSearchParams(window.location.search) 20 const feedFromUrl = params.get('feed') ··· 49 const saveState = useCallback( 50 (feed: FeedDescriptor) => { 51 setState(feed) 52 + if (IS_WEB) { 53 try { 54 sessionStorage.setItem('lastSelectedHomeFeed', feed) 55 } catch {}
+2 -2
src/view/com/auth/SplashScreen.web.tsx
··· 31 }) => { 32 const {_} = useLingui() 33 const t = useTheme() 34 - const {isTabletOrMobile: isMobileWeb} = useWebMediaQueries() 35 const [showClipOverlay, setShowClipOverlay] = React.useState(false) 36 37 React.useEffect(() => { ··· 78 a.justify_center, 79 // @ts-expect-error web only 80 {paddingBottom: '20vh'}, 81 - isMobileWeb && a.pb_5xl, 82 t.atoms.border_contrast_medium, 83 a.align_center, 84 a.gap_5xl,
··· 31 }) => { 32 const {_} = useLingui() 33 const t = useTheme() 34 + const {isTabletOrMobile: IS_WEB_MOBILE} = useWebMediaQueries() 35 const [showClipOverlay, setShowClipOverlay] = React.useState(false) 36 37 React.useEffect(() => { ··· 78 a.justify_center, 79 // @ts-expect-error web only 80 {paddingBottom: '20vh'}, 81 + IS_WEB_MOBILE && a.pb_5xl, 82 t.atoms.border_contrast_medium, 83 a.align_center, 84 a.gap_5xl,
+19 -19
src/view/com/composer/Composer.tsx
··· 76 import {cleanError} from '#/lib/strings/errors' 77 import {colors} from '#/lib/styles' 78 import {logger} from '#/logger' 79 - import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection' 80 import {useDialogStateControlContext} from '#/state/dialogs' 81 import {emitPostCreated} from '#/state/events' 82 import { ··· 132 import * as Prompt from '#/components/Prompt' 133 import * as Toast from '#/components/Toast' 134 import {Text as NewText} from '#/components/Typography' 135 import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' 136 import {PostLanguageSelect} from './select-language/PostLanguageSelect' 137 import { ··· 331 const insets = useSafeAreaInsets() 332 const viewStyles = useMemo( 333 () => ({ 334 - paddingTop: isAndroid ? insets.top : 0, 335 paddingBottom: 336 // iOS - when keyboard is closed, keep the bottom bar in the safe area 337 - (isIOS && !isKeyboardVisible) || 338 // Android - Android >=35 KeyboardAvoidingView adds double padding when 339 // keyboard is closed, so we subtract that in the offset and add it back 340 // here when the keyboard is open 341 - (isAndroid && isKeyboardVisible) 342 ? insets.bottom 343 : 0, 344 }), ··· 368 369 // On Android, pressing Back should ask confirmation. 370 useEffect(() => { 371 - if (!isAndroid) { 372 return 373 } 374 const backHandler = BackHandler.addEventListener( ··· 673 composerState.mutableNeedsFocusActive = false 674 // On Android, this risks getting the cursor stuck behind the keyboard. 675 // Not worth it. 676 - if (!isAndroid) { 677 textInput.current?.focus() 678 } 679 } ··· 729 </> 730 ) 731 732 - const isWebFooterSticky = !isNative && thread.posts.length > 1 733 return ( 734 <BottomSheetPortalProvider> 735 <KeyboardAvoidingView 736 testID="composePostView" 737 - behavior={isIOS ? 'padding' : 'height'} 738 keyboardVerticalOffset={keyboardVerticalOffset} 739 style={a.flex_1}> 740 <View ··· 794 onPublish={onComposerPostPublish} 795 onError={setError} 796 /> 797 - {isWebFooterSticky && post.id === activePost.id && ( 798 <View style={styles.stickyFooterWeb}>{footer}</View> 799 )} 800 </React.Fragment> 801 ))} 802 </Animated.ScrollView> 803 - {!isWebFooterSticky && footer} 804 </View> 805 806 <Prompt.Basic ··· 853 const {data: currentProfile} = useProfileQuery({did: currentDid}) 854 const richtext = post.richtext 855 const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media 856 - const forceMinHeight = isWeb && isTextOnly && isActive 857 const selectTextInputPlaceholder = isReply 858 ? isFirstPost 859 ? _(msg`Write your reply`) ··· 895 async (uri: string) => { 896 if ( 897 uri.startsWith('data:video/') || 898 - (isWeb && uri.startsWith('data:image/gif')) 899 ) { 900 - if (isNative) return // web only 901 const [mimeType] = uri.slice('data:'.length).split(';') 902 if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) { 903 Toast.show(_(msg`Unsupported video type: ${mimeType}`), { ··· 927 a.mb_sm, 928 !isActive && isLastPost && a.mb_lg, 929 !isActive && styles.inactivePost, 930 - isTextOnly && isNative && a.flex_grow, 931 ]}> 932 - <View style={[a.flex_row, isNative && a.flex_1]}> 933 <UserAvatar 934 avatar={currentProfile?.avatar} 935 size={42} ··· 1270 </LayoutAnimationConfig> 1271 {embed.quote?.uri ? ( 1272 <View 1273 - style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], isWeb && [a.pb_md]]}> 1274 <View style={[a.relative]}> 1275 <View style={{pointerEvents: 'none'}}> 1276 <LazyQuoteEmbed uri={embed.quote.uri} /> ··· 1683 const {top, bottom} = useSafeAreaInsets() 1684 1685 // Android etc 1686 - if (!isIOS) { 1687 // need to account for the edge-to-edge nav bar 1688 return bottom * -1 1689 } ··· 1727 const appState = useAppState() 1728 1729 useEffect(() => { 1730 - if (isIOS) { 1731 if (appState === 'inactive') { 1732 Keyboard.dismiss() 1733 } ··· 1879 style: StyleProp<ViewStyle> 1880 children: React.ReactNode 1881 }) { 1882 - if (isWeb) return children 1883 return ( 1884 <Animated.View 1885 style={style}
··· 76 import {cleanError} from '#/lib/strings/errors' 77 import {colors} from '#/lib/styles' 78 import {logger} from '#/logger' 79 import {useDialogStateControlContext} from '#/state/dialogs' 80 import {emitPostCreated} from '#/state/events' 81 import { ··· 131 import * as Prompt from '#/components/Prompt' 132 import * as Toast from '#/components/Toast' 133 import {Text as NewText} from '#/components/Typography' 134 + import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 135 import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' 136 import {PostLanguageSelect} from './select-language/PostLanguageSelect' 137 import { ··· 331 const insets = useSafeAreaInsets() 332 const viewStyles = useMemo( 333 () => ({ 334 + paddingTop: IS_ANDROID ? insets.top : 0, 335 paddingBottom: 336 // iOS - when keyboard is closed, keep the bottom bar in the safe area 337 + (IS_IOS && !isKeyboardVisible) || 338 // Android - Android >=35 KeyboardAvoidingView adds double padding when 339 // keyboard is closed, so we subtract that in the offset and add it back 340 // here when the keyboard is open 341 + (IS_ANDROID && isKeyboardVisible) 342 ? insets.bottom 343 : 0, 344 }), ··· 368 369 // On Android, pressing Back should ask confirmation. 370 useEffect(() => { 371 + if (!IS_ANDROID) { 372 return 373 } 374 const backHandler = BackHandler.addEventListener( ··· 673 composerState.mutableNeedsFocusActive = false 674 // On Android, this risks getting the cursor stuck behind the keyboard. 675 // Not worth it. 676 + if (!IS_ANDROID) { 677 textInput.current?.focus() 678 } 679 } ··· 729 </> 730 ) 731 732 + const IS_WEBFooterSticky = !IS_NATIVE && thread.posts.length > 1 733 return ( 734 <BottomSheetPortalProvider> 735 <KeyboardAvoidingView 736 testID="composePostView" 737 + behavior={IS_IOS ? 'padding' : 'height'} 738 keyboardVerticalOffset={keyboardVerticalOffset} 739 style={a.flex_1}> 740 <View ··· 794 onPublish={onComposerPostPublish} 795 onError={setError} 796 /> 797 + {IS_WEBFooterSticky && post.id === activePost.id && ( 798 <View style={styles.stickyFooterWeb}>{footer}</View> 799 )} 800 </React.Fragment> 801 ))} 802 </Animated.ScrollView> 803 + {!IS_WEBFooterSticky && footer} 804 </View> 805 806 <Prompt.Basic ··· 853 const {data: currentProfile} = useProfileQuery({did: currentDid}) 854 const richtext = post.richtext 855 const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media 856 + const forceMinHeight = IS_WEB && isTextOnly && isActive 857 const selectTextInputPlaceholder = isReply 858 ? isFirstPost 859 ? _(msg`Write your reply`) ··· 895 async (uri: string) => { 896 if ( 897 uri.startsWith('data:video/') || 898 + (IS_WEB && uri.startsWith('data:image/gif')) 899 ) { 900 + if (IS_NATIVE) return // web only 901 const [mimeType] = uri.slice('data:'.length).split(';') 902 if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) { 903 Toast.show(_(msg`Unsupported video type: ${mimeType}`), { ··· 927 a.mb_sm, 928 !isActive && isLastPost && a.mb_lg, 929 !isActive && styles.inactivePost, 930 + isTextOnly && IS_NATIVE && a.flex_grow, 931 ]}> 932 + <View style={[a.flex_row, IS_NATIVE && a.flex_1]}> 933 <UserAvatar 934 avatar={currentProfile?.avatar} 935 size={42} ··· 1270 </LayoutAnimationConfig> 1271 {embed.quote?.uri ? ( 1272 <View 1273 + style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], IS_WEB && [a.pb_md]]}> 1274 <View style={[a.relative]}> 1275 <View style={{pointerEvents: 'none'}}> 1276 <LazyQuoteEmbed uri={embed.quote.uri} /> ··· 1683 const {top, bottom} = useSafeAreaInsets() 1684 1685 // Android etc 1686 + if (!IS_IOS) { 1687 // need to account for the edge-to-edge nav bar 1688 return bottom * -1 1689 } ··· 1727 const appState = useAppState() 1728 1729 useEffect(() => { 1730 + if (IS_IOS) { 1731 if (appState === 'inactive') { 1732 Keyboard.dismiss() 1733 } ··· 1879 style: StyleProp<ViewStyle> 1880 children: React.ReactNode 1881 }) { 1882 + if (IS_WEB) return children 1883 return ( 1884 <Animated.View 1885 style={style}
+2 -2
src/view/com/composer/GifAltText.tsx
··· 9 type EmbedPlayerParams, 10 parseEmbedPlayerFromUrl, 11 } from '#/lib/strings/embed-player' 12 - import {isAndroid} from '#/platform/detection' 13 import {useResolveGifQuery} from '#/state/queries/resolve-link' 14 import {type Gif} from '#/state/queries/tenor' 15 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' ··· 23 import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 24 import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif' 25 import {Text} from '#/components/Typography' 26 import {AltTextReminder} from './photos/Gallery' 27 28 export function GifAltTextDialog({ ··· 224 </View> 225 <Dialog.Close /> 226 {/* Maybe fix this later -h */} 227 - {isAndroid ? <View style={{height: 300}} /> : null} 228 </Dialog.ScrollableInner> 229 ) 230 }
··· 9 type EmbedPlayerParams, 10 parseEmbedPlayerFromUrl, 11 } from '#/lib/strings/embed-player' 12 import {useResolveGifQuery} from '#/state/queries/resolve-link' 13 import {type Gif} from '#/state/queries/tenor' 14 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' ··· 22 import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 23 import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif' 24 import {Text} from '#/components/Typography' 25 + import {IS_ANDROID} from '#/env' 26 import {AltTextReminder} from './photos/Gallery' 27 28 export function GifAltTextDialog({ ··· 224 </View> 225 <Dialog.Close /> 226 {/* Maybe fix this later -h */} 227 + {IS_ANDROID ? <View style={{height: 300}} /> : null} 228 </Dialog.ScrollableInner> 229 ) 230 }
+2 -2
src/view/com/composer/KeyboardAccessory.tsx
··· 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 import type React from 'react' 5 6 - import {isWeb} from '#/platform/detection' 7 import {atoms as a, useTheme} from '#/alf' 8 9 export function KeyboardAccessory({children}: {children: React.ReactNode}) { 10 const t = useTheme() ··· 22 ] 23 24 // todo: when iPad support is added, it should also not use the KeyboardStickyView 25 - if (isWeb) { 26 return <View style={style}>{children}</View> 27 } 28
··· 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 import type React from 'react' 5 6 import {atoms as a, useTheme} from '#/alf' 7 + import {IS_WEB} from '#/env' 8 9 export function KeyboardAccessory({children}: {children: React.ReactNode}) { 10 const t = useTheme() ··· 22 ] 23 24 // todo: when iPad support is added, it should also not use the KeyboardStickyView 25 + if (IS_WEB) { 26 return <View style={style}>{children}</View> 27 } 28
+9 -9
src/view/com/composer/SelectMediaButton.tsx
··· 11 } from '#/lib/hooks/usePermissions' 12 import {openUnifiedPicker} from '#/lib/media/picker' 13 import {extractDataUriMime} from '#/lib/media/util' 14 - import {isNative, isWeb} from '#/platform/detection' 15 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 16 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 17 import {atoms as a, useTheme} from '#/alf' ··· 19 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' 20 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 21 import * as toast from '#/components/Toast' 22 23 export type SelectMediaButtonProps = { 24 disabled?: boolean ··· 92 'image/svg+xml', 93 'image/webp', 94 'image/avif', 95 - isNative && 'image/heic', 96 ] as const 97 ).filter(Boolean) 98 type SupportedImageMimeType = Exclude< ··· 262 * We don't care too much about mimeType at this point on native, 263 * since the `processVideo` step later on will convert to `.mp4`. 264 */ 265 - if (isWeb && !isSupportedVideoMimeType(mimeType)) { 266 errors.add(SelectedAssetError.Unsupported) 267 continue 268 } ··· 272 * to filter out large files on web. On native, we compress these anyway, 273 * so we only check on web. 274 */ 275 - if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 276 errors.add(SelectedAssetError.FileTooBig) 277 continue 278 } ··· 291 * to filter out large files on web. On native, we compress GIFs as 292 * videos anyway, so we only check on web. 293 */ 294 - if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 295 errors.add(SelectedAssetError.FileTooBig) 296 continue 297 } ··· 309 * base64 data-uri, so we construct it here for web only. 310 */ 311 uri: 312 - isWeb && asset.base64 313 ? `data:${mimeType};base64,${asset.base64}` 314 : asset.uri, 315 }) ··· 328 } 329 330 if (supportedAssets[0].duration) { 331 - if (isWeb) { 332 /* 333 * Web reports duration as seconds 334 */ ··· 433 ) 434 435 const onPressSelectMedia = useCallback(async () => { 436 - if (isNative) { 437 const [photoAccess, videoAccess] = await Promise.all([ 438 requestPhotoAccessIfNeeded(), 439 requestVideoAccessIfNeeded(), ··· 447 } 448 } 449 450 - if (isNative && Keyboard.isVisible()) { 451 Keyboard.dismiss() 452 } 453
··· 11 } from '#/lib/hooks/usePermissions' 12 import {openUnifiedPicker} from '#/lib/media/picker' 13 import {extractDataUriMime} from '#/lib/media/util' 14 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 15 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 16 import {atoms as a, useTheme} from '#/alf' ··· 18 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' 19 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 20 import * as toast from '#/components/Toast' 21 + import {IS_NATIVE, IS_WEB} from '#/env' 22 23 export type SelectMediaButtonProps = { 24 disabled?: boolean ··· 92 'image/svg+xml', 93 'image/webp', 94 'image/avif', 95 + IS_NATIVE && 'image/heic', 96 ] as const 97 ).filter(Boolean) 98 type SupportedImageMimeType = Exclude< ··· 262 * We don't care too much about mimeType at this point on native, 263 * since the `processVideo` step later on will convert to `.mp4`. 264 */ 265 + if (IS_WEB && !isSupportedVideoMimeType(mimeType)) { 266 errors.add(SelectedAssetError.Unsupported) 267 continue 268 } ··· 272 * to filter out large files on web. On native, we compress these anyway, 273 * so we only check on web. 274 */ 275 + if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 276 errors.add(SelectedAssetError.FileTooBig) 277 continue 278 } ··· 291 * to filter out large files on web. On native, we compress GIFs as 292 * videos anyway, so we only check on web. 293 */ 294 + if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 295 errors.add(SelectedAssetError.FileTooBig) 296 continue 297 } ··· 309 * base64 data-uri, so we construct it here for web only. 310 */ 311 uri: 312 + IS_WEB && asset.base64 313 ? `data:${mimeType};base64,${asset.base64}` 314 : asset.uri, 315 }) ··· 328 } 329 330 if (supportedAssets[0].duration) { 331 + if (IS_WEB) { 332 /* 333 * Web reports duration as seconds 334 */ ··· 433 ) 434 435 const onPressSelectMedia = useCallback(async () => { 436 + if (IS_NATIVE) { 437 const [photoAccess, videoAccess] = await Promise.all([ 438 requestPhotoAccessIfNeeded(), 439 requestVideoAccessIfNeeded(), ··· 447 } 448 } 449 450 + if (IS_NATIVE && Keyboard.isVisible()) { 451 Keyboard.dismiss() 452 } 453
+2 -2
src/view/com/composer/labels/LabelsBtn.tsx
··· 9 type OtherSelfLabel, 10 type SelfLabel, 11 } from '#/lib/moderation' 12 - import {isWeb} from '#/platform/detection' 13 import {atoms as a, useTheme, web} from '#/alf' 14 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 import * as Dialog from '#/components/Dialog' ··· 18 import {TinyChevronBottom_Stroke2_Corner0_Rounded as TinyChevronIcon} from '#/components/icons/Chevron' 19 import {Shield_Stroke2_Corner0_Rounded} from '#/components/icons/Shield' 20 import {Text} from '#/components/Typography' 21 22 export function LabelsBtn({ 23 labels, ··· 218 label={_(msg`Done`)} 219 onPress={() => control.close()} 220 color="primary" 221 - size={isWeb ? 'small' : 'large'} 222 variant="solid" 223 testID="confirmBtn"> 224 <ButtonText>
··· 9 type OtherSelfLabel, 10 type SelfLabel, 11 } from '#/lib/moderation' 12 import {atoms as a, useTheme, web} from '#/alf' 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 import * as Dialog from '#/components/Dialog' ··· 17 import {TinyChevronBottom_Stroke2_Corner0_Rounded as TinyChevronIcon} from '#/components/icons/Chevron' 18 import {Shield_Stroke2_Corner0_Rounded} from '#/components/icons/Shield' 19 import {Text} from '#/components/Typography' 20 + import {IS_WEB} from '#/env' 21 22 export function LabelsBtn({ 23 labels, ··· 218 label={_(msg`Done`)} 219 onPress={() => control.close()} 220 color="primary" 221 + size={IS_WEB ? 'small' : 'large'} 222 variant="solid" 223 testID="confirmBtn"> 224 <ButtonText>
+2 -2
src/view/com/composer/photos/Gallery.tsx
··· 16 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 17 import {type Dimensions} from '#/lib/media/types' 18 import {colors, s} from '#/lib/styles' 19 - import {isNative} from '#/platform/detection' 20 import {type ComposerImage, cropImage} from '#/state/gallery' 21 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 22 import {Text} from '#/view/com/util/text/Text' 23 import {tokens, useTheme} from '#/alf' 24 import * as Dialog from '#/components/Dialog' 25 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 26 import {type PostAction} from '../state/composer' 27 import {EditImageDialog} from './EditImageDialog' 28 import {ImageAltTextDialog} from './ImageAltTextDialog' ··· 148 const enableSquareButtons = useEnableSquareButtons() 149 150 const onImageEdit = () => { 151 - if (isNative) { 152 cropImage(image).then(next => { 153 onChange(next) 154 })
··· 16 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 17 import {type Dimensions} from '#/lib/media/types' 18 import {colors, s} from '#/lib/styles' 19 import {type ComposerImage, cropImage} from '#/state/gallery' 20 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 21 import {Text} from '#/view/com/util/text/Text' 22 import {tokens, useTheme} from '#/alf' 23 import * as Dialog from '#/components/Dialog' 24 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 25 + import {IS_NATIVE} from '#/env' 26 import {type PostAction} from '../state/composer' 27 import {EditImageDialog} from './EditImageDialog' 28 import {ImageAltTextDialog} from './ImageAltTextDialog' ··· 148 const enableSquareButtons = useEnableSquareButtons() 149 150 const onImageEdit = () => { 151 + if (IS_NATIVE) { 152 cropImage(image).then(next => { 153 onChange(next) 154 })
+3 -3
src/view/com/composer/photos/ImageAltTextDialog.tsx
··· 6 7 import {MAX_ALT_TEXT} from '#/lib/constants' 8 import {enforceLen} from '#/lib/strings/helpers' 9 - import {isAndroid, isWeb} from '#/platform/detection' 10 import {type ComposerImage} from '#/state/gallery' 11 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' 12 import {atoms as a, useTheme} from '#/alf' ··· 16 import * as TextField from '#/components/forms/TextField' 17 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 18 import {Text} from '#/components/Typography' 19 20 type Props = { 21 control: Dialog.DialogOuterProps['control'] ··· 66 const windim = useWindowDimensions() 67 68 const imageStyle = React.useMemo<ImageStyle>(() => { 69 - const maxWidth = isWeb ? 450 : windim.width 70 const source = image.transformed ?? image.source 71 72 if (source.height > source.width) { ··· 165 </AltTextCounterWrapper> 166 </View> 167 {/* Maybe fix this later -h */} 168 - {isAndroid ? <View style={{height: 300}} /> : null} 169 </Dialog.ScrollableInner> 170 ) 171 }
··· 6 7 import {MAX_ALT_TEXT} from '#/lib/constants' 8 import {enforceLen} from '#/lib/strings/helpers' 9 import {type ComposerImage} from '#/state/gallery' 10 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' 11 import {atoms as a, useTheme} from '#/alf' ··· 15 import * as TextField from '#/components/forms/TextField' 16 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 17 import {Text} from '#/components/Typography' 18 + import {IS_ANDROID, IS_WEB} from '#/env' 19 20 type Props = { 21 control: Dialog.DialogOuterProps['control'] ··· 66 const windim = useWindowDimensions() 67 68 const imageStyle = React.useMemo<ImageStyle>(() => { 69 + const maxWidth = IS_WEB ? 450 : windim.width 70 const source = image.transformed ?? image.source 71 72 if (source.height > source.width) { ··· 165 </AltTextCounterWrapper> 166 </View> 167 {/* Maybe fix this later -h */} 168 + {IS_ANDROID ? <View style={{height: 300}} /> : null} 169 </Dialog.ScrollableInner> 170 ) 171 }
+2 -2
src/view/com/composer/photos/OpenCameraBtn.tsx
··· 7 import {useCameraPermission} from '#/lib/hooks/usePermissions' 8 import {openCamera} from '#/lib/media/picker' 9 import {logger} from '#/logger' 10 - import {isMobileWeb, isNative} from '#/platform/detection' 11 import {type ComposerImage, createComposerImage} from '#/state/gallery' 12 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 13 import {atoms as a, useTheme} from '#/alf' 14 import {Button} from '#/components/Button' 15 import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera' 16 17 type Props = { 18 disabled?: boolean ··· 61 requestMediaPermission, 62 ]) 63 64 - const shouldShowCameraButton = isNative || isMobileWeb 65 if (!shouldShowCameraButton) { 66 return null 67 }
··· 7 import {useCameraPermission} from '#/lib/hooks/usePermissions' 8 import {openCamera} from '#/lib/media/picker' 9 import {logger} from '#/logger' 10 import {type ComposerImage, createComposerImage} from '#/state/gallery' 11 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 12 import {atoms as a, useTheme} from '#/alf' 13 import {Button} from '#/components/Button' 14 import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera' 15 + import {IS_NATIVE, IS_WEB_MOBILE} from '#/env' 16 17 type Props = { 18 disabled?: boolean ··· 61 requestMediaPermission, 62 ]) 63 64 + const shouldShowCameraButton = IS_NATIVE || IS_WEB_MOBILE 65 if (!shouldShowCameraButton) { 66 return null 67 }
+4 -4
src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
··· 6 7 import {languageName} from '#/locale/helpers' 8 import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages' 9 - import {isNative, isWeb} from '#/platform/detection' 10 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11 import { 12 toPostLanguages, ··· 22 import * as Toggle from '#/components/forms/Toggle' 23 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 24 import {Text} from '#/components/Typography' 25 26 export function PostLanguageSelectDialog({ 27 control, ··· 171 172 const listHeader = ( 173 <View 174 - style={[a.pb_xs, t.atoms.bg, isNative && a.pt_2xl]} 175 onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}> 176 <View style={[a.flex_row, a.w_full, a.justify_between]}> 177 <View> ··· 198 </Text> 199 </View> 200 201 - {isWeb && ( 202 <Button 203 variant="ghost" 204 size="small" ··· 255 ListHeaderComponent={listHeader} 256 stickyHeaderIndices={[0]} 257 contentContainerStyle={[a.gap_0]} 258 - style={[isNative && a.px_lg, web({paddingBottom: 120})]} 259 scrollIndicatorInsets={{top: headerHeight}} 260 renderItem={({item, index}) => { 261 if (item.type === 'header') {
··· 6 7 import {languageName} from '#/locale/helpers' 8 import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages' 9 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 10 import { 11 toPostLanguages, ··· 21 import * as Toggle from '#/components/forms/Toggle' 22 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 23 import {Text} from '#/components/Typography' 24 + import {IS_NATIVE, IS_WEB} from '#/env' 25 26 export function PostLanguageSelectDialog({ 27 control, ··· 171 172 const listHeader = ( 173 <View 174 + style={[a.pb_xs, t.atoms.bg, IS_NATIVE && a.pt_2xl]} 175 onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}> 176 <View style={[a.flex_row, a.w_full, a.justify_between]}> 177 <View> ··· 198 </Text> 199 </View> 200 201 + {IS_WEB && ( 202 <Button 203 variant="ghost" 204 size="small" ··· 255 ListHeaderComponent={listHeader} 256 stickyHeaderIndices={[0]} 257 contentContainerStyle={[a.gap_0]} 258 + style={[IS_NATIVE && a.px_lg, web({paddingBottom: 120})]} 259 scrollIndicatorInsets={{top: headerHeight}} 260 renderItem={({item, index}) => { 261 if (item.type === 'header') {
+6 -4
src/view/com/composer/text-input/TextInput.tsx
··· 14 import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api' 15 import PasteInput, { 16 type PastedFile, 17 - type PasteInputRef, // @ts-expect-error no types when installing from github 18 } from '@mattermost/react-native-paste-input' 19 20 import {POST_IMG_MAX} from '#/lib/constants' ··· 23 import {cleanError} from '#/lib/strings/errors' 24 import {getMentionAt, insertMentionAt} from '#/lib/strings/mention-manip' 25 import {useTheme} from '#/lib/ThemeContext' 26 - import {isAndroid, isNative} from '#/platform/detection' 27 import { 28 type LinkFacetMatch, 29 suggestLinkCardUri, 30 } from '#/view/com/composer/text-input/text-input-util' 31 import {atoms as a, useAlf} from '#/alf' 32 import {normalizeTextStyles} from '#/alf/typography' 33 import {Autocomplete} from './mobile/Autocomplete' 34 import {type TextInputProps} from './TextInput.types' 35 ··· 226 /** 227 * PasteInput doesn't like `lineHeight`, results in jumpiness 228 */ 229 - if (isNative) { 230 style.lineHeight = undefined 231 } 232 233 /* 234 * Android impl of `PasteInput` doesn't support the array syntax for `fontVariant` 235 */ 236 - if (isAndroid) { 237 // @ts-ignore 238 style.fontVariant = style.fontVariant 239 ? style.fontVariant.join(' ')
··· 14 import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api' 15 import PasteInput, { 16 type PastedFile, 17 + type PasteInputRef, 18 + // @ts-expect-error no types when installing from github 19 + // eslint-disable-next-line import-x/no-unresolved 20 } from '@mattermost/react-native-paste-input' 21 22 import {POST_IMG_MAX} from '#/lib/constants' ··· 25 import {cleanError} from '#/lib/strings/errors' 26 import {getMentionAt, insertMentionAt} from '#/lib/strings/mention-manip' 27 import {useTheme} from '#/lib/ThemeContext' 28 import { 29 type LinkFacetMatch, 30 suggestLinkCardUri, 31 } from '#/view/com/composer/text-input/text-input-util' 32 import {atoms as a, useAlf} from '#/alf' 33 import {normalizeTextStyles} from '#/alf/typography' 34 + import {IS_ANDROID, IS_NATIVE} from '#/env' 35 import {Autocomplete} from './mobile/Autocomplete' 36 import {type TextInputProps} from './TextInput.types' 37 ··· 228 /** 229 * PasteInput doesn't like `lineHeight`, results in jumpiness 230 */ 231 + if (IS_NATIVE) { 232 style.lineHeight = undefined 233 } 234 235 /* 236 * Android impl of `PasteInput` doesn't support the array syntax for `fontVariant` 237 */ 238 + if (IS_ANDROID) { 239 // @ts-ignore 240 style.fontVariant = style.fontVariant 241 ? style.fontVariant.join(' ')
+2 -2
src/view/com/composer/threadgate/ThreadgateBtn.tsx
··· 8 9 import {isNetworkError} from '#/lib/strings/errors' 10 import {logger} from '#/logger' 11 - import {isNative} from '#/platform/detection' 12 import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings' 13 import {createPostgateRecord} from '#/state/queries/postgate/util' 14 import {usePreferencesQuery} from '#/state/queries/preferences' ··· 25 import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' 26 import * as Tooltip from '#/components/Tooltip' 27 import {Text} from '#/components/Typography' 28 import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged' 29 30 export function ThreadgateBtn({ ··· 70 nudged: tooltipWasShown, 71 }) 72 73 - if (isNative && Keyboard.isVisible()) { 74 Keyboard.dismiss() 75 } 76
··· 8 9 import {isNetworkError} from '#/lib/strings/errors' 10 import {logger} from '#/logger' 11 import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings' 12 import {createPostgateRecord} from '#/state/queries/postgate/util' 13 import {usePreferencesQuery} from '#/state/queries/preferences' ··· 24 import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' 25 import * as Tooltip from '#/components/Tooltip' 26 import {Text} from '#/components/Typography' 27 + import {IS_NATIVE} from '#/env' 28 import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged' 29 30 export function ThreadgateBtn({ ··· 70 nudged: tooltipWasShown, 71 }) 72 73 + if (IS_NATIVE && Keyboard.isVisible()) { 74 Keyboard.dismiss() 75 } 76
+10 -6
src/view/com/composer/videos/SubtitleDialog.tsx
··· 6 import {MAX_ALT_TEXT} from '#/lib/constants' 7 import {isOverMaxGraphemeCount} from '#/lib/strings/helpers' 8 import {LANGUAGES} from '#/locale/languages' 9 - import {isWeb} from '#/platform/detection' 10 import {useLanguagePrefs} from '#/state/preferences' 11 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 12 import {atoms as a, useTheme, web} from '#/alf' ··· 18 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 19 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 20 import {Text} from '#/components/Typography' 21 import {SubtitleFilePicker} from './SubtitleFilePicker' 22 23 const MAX_NUM_CAPTIONS = 1 ··· 38 return ( 39 <View style={[a.flex_row, a.my_xs]}> 40 <Button 41 - label={isWeb ? _(msg`Captions & alt text`) : _(msg`Alt text`)} 42 accessibilityHint={ 43 - isWeb 44 ? _(msg`Opens captions and alt text dialog`) 45 : _(msg`Opens alt text dialog`) 46 } ··· 53 }}> 54 <ButtonIcon icon={CCIcon} /> 55 <ButtonText> 56 - {isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>} 57 </ButtonText> 58 </Button> 59 <Dialog.Outer control={control}> ··· 135 </Text> 136 )} 137 138 - {isWeb && ( 139 <> 140 <View 141 style={[ ··· 183 <View style={web([a.flex_row, a.justify_end])}> 184 <Button 185 label={_(msg`Done`)} 186 - size={isWeb ? 'small' : 'large'} 187 color="primary" 188 variant="solid" 189 onPress={() => {
··· 6 import {MAX_ALT_TEXT} from '#/lib/constants' 7 import {isOverMaxGraphemeCount} from '#/lib/strings/helpers' 8 import {LANGUAGES} from '#/locale/languages' 9 import {useLanguagePrefs} from '#/state/preferences' 10 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11 import {atoms as a, useTheme, web} from '#/alf' ··· 17 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 18 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 19 import {Text} from '#/components/Typography' 20 + import {IS_WEB} from '#/env' 21 import {SubtitleFilePicker} from './SubtitleFilePicker' 22 23 const MAX_NUM_CAPTIONS = 1 ··· 38 return ( 39 <View style={[a.flex_row, a.my_xs]}> 40 <Button 41 + label={IS_WEB ? _(msg`Captions & alt text`) : _(msg`Alt text`)} 42 accessibilityHint={ 43 + IS_WEB 44 ? _(msg`Opens captions and alt text dialog`) 45 : _(msg`Opens alt text dialog`) 46 } ··· 53 }}> 54 <ButtonIcon icon={CCIcon} /> 55 <ButtonText> 56 + {IS_WEB ? ( 57 + <Trans>Captions & alt text</Trans> 58 + ) : ( 59 + <Trans>Alt text</Trans> 60 + )} 61 </ButtonText> 62 </Button> 63 <Dialog.Outer control={control}> ··· 139 </Text> 140 )} 141 142 + {IS_WEB && ( 143 <> 144 <View 145 style={[ ··· 187 <View style={web([a.flex_row, a.justify_end])}> 188 <Button 189 label={_(msg`Done`)} 190 + size={IS_WEB ? 'small' : 'large'} 191 color="primary" 192 variant="solid" 193 onPress={() => {
+2 -2
src/view/com/composer/videos/VideoTranscodeProgress.tsx
··· 4 import {type ImagePickerAsset} from 'expo-image-picker' 5 6 import {clamp} from '#/lib/numbers' 7 - import {isWeb} from '#/platform/detection' 8 import {atoms as a, useTheme} from '#/alf' 9 import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn' 10 import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop' 11 ··· 20 }) { 21 const t = useTheme() 22 23 - if (isWeb) return null 24 25 let aspectRatio = asset.width / asset.height 26
··· 4 import {type ImagePickerAsset} from 'expo-image-picker' 5 6 import {clamp} from '#/lib/numbers' 7 import {atoms as a, useTheme} from '#/alf' 8 + import {IS_WEB} from '#/env' 9 import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn' 10 import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop' 11 ··· 20 }) { 21 const t = useTheme() 22 23 + if (IS_WEB) return null 24 25 let aspectRatio = asset.width / asset.height 26
+5 -5
src/view/com/feeds/ComposerPrompt.tsx
··· 11 } from '#/lib/hooks/usePermissions' 12 import {openCamera, openUnifiedPicker} from '#/lib/media/picker' 13 import {logger} from '#/logger' 14 - import {isNative} from '#/platform/detection' 15 import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile' 16 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 17 import {UserAvatar} from '#/view/com/util/UserAvatar' ··· 22 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 23 import {SubtleHover} from '#/components/SubtleHover' 24 import {Text} from '#/components/Typography' 25 26 export function ComposerPrompt() { 27 const {_} = useLingui() ··· 43 logger.metric('composerPrompt:gallery:press', {}) 44 45 // On web, open the composer with the gallery picker auto-opening 46 - if (!isNative) { 47 openComposer({openGallery: true}) 48 return 49 } ··· 105 return 106 } 107 108 - if (isNative && Keyboard.isVisible()) { 109 Keyboard.dismiss() 110 } 111 ··· 122 ] 123 124 openComposer({ 125 - imageUris: isNative ? imageUris : undefined, 126 }) 127 } catch (err: any) { 128 if (!String(err).toLowerCase().includes('cancel')) { ··· 189 <Trans>What's up?</Trans> 190 </Text> 191 <View style={[a.flex_row, a.gap_md]}> 192 - {isNative && ( 193 <Button 194 onPress={e => { 195 e.stopPropagation()
··· 11 } from '#/lib/hooks/usePermissions' 12 import {openCamera, openUnifiedPicker} from '#/lib/media/picker' 13 import {logger} from '#/logger' 14 import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile' 15 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 16 import {UserAvatar} from '#/view/com/util/UserAvatar' ··· 21 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 22 import {SubtleHover} from '#/components/SubtleHover' 23 import {Text} from '#/components/Typography' 24 + import {IS_NATIVE} from '#/env' 25 26 export function ComposerPrompt() { 27 const {_} = useLingui() ··· 43 logger.metric('composerPrompt:gallery:press', {}) 44 45 // On web, open the composer with the gallery picker auto-opening 46 + if (!IS_NATIVE) { 47 openComposer({openGallery: true}) 48 return 49 } ··· 105 return 106 } 107 108 + if (IS_NATIVE && Keyboard.isVisible()) { 109 Keyboard.dismiss() 110 } 111 ··· 122 ] 123 124 openComposer({ 125 + imageUris: IS_NATIVE ? imageUris : undefined, 126 }) 127 } catch (err: any) { 128 if (!String(err).toLowerCase().includes('cancel')) { ··· 189 <Trans>What's up?</Trans> 190 </Text> 191 <View style={[a.flex_row, a.gap_md]}> 192 + {IS_NATIVE && ( 193 <Button 194 onPress={e => { 195 e.stopPropagation()
+4 -4
src/view/com/feeds/FeedPage.tsx
··· 20 import {type AllNavigatorParams} from '#/lib/routes/types' 21 import {logEvent} from '#/lib/statsig/statsig' 22 import {s} from '#/lib/styles' 23 - import {isNative} from '#/platform/detection' 24 import {listenSoftReset} from '#/state/events' 25 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 26 import {useSetHomeBadge} from '#/state/home-badge' ··· 34 import {useSession} from '#/state/session' 35 import {useSetMinimalShellMode} from '#/state/shell' 36 import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' 37 import {PostFeed} from '../posts/PostFeed' 38 import {FAB} from '../util/fab/FAB' 39 import {type ListMethods} from '../util/List' ··· 80 const feedIsVideoMode = 81 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO 82 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode 83 - return isNative && _isVideoFeed 84 }, [feedInfo]) 85 86 useEffect(() => { ··· 91 92 const scrollToTop = useCallback(() => { 93 scrollElRef.current?.scrollToOffset({ 94 - animated: isNative, 95 offset: -headerOffset, 96 }) 97 setMinimalShellMode(false) ··· 136 }) 137 }, [scrollToTop, feed, queryClient]) 138 139 - const shouldPrefetch = isNative && isPageAdjacent 140 const isDiscoverFeed = feedInfo.uri === DISCOVER_FEED_URI 141 return ( 142 <View
··· 20 import {type AllNavigatorParams} from '#/lib/routes/types' 21 import {logEvent} from '#/lib/statsig/statsig' 22 import {s} from '#/lib/styles' 23 import {listenSoftReset} from '#/state/events' 24 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 25 import {useSetHomeBadge} from '#/state/home-badge' ··· 33 import {useSession} from '#/state/session' 34 import {useSetMinimalShellMode} from '#/state/shell' 35 import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' 36 + import {IS_NATIVE} from '#/env' 37 import {PostFeed} from '../posts/PostFeed' 38 import {FAB} from '../util/fab/FAB' 39 import {type ListMethods} from '../util/List' ··· 80 const feedIsVideoMode = 81 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO 82 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode 83 + return IS_NATIVE && _isVideoFeed 84 }, [feedInfo]) 85 86 useEffect(() => { ··· 91 92 const scrollToTop = useCallback(() => { 93 scrollElRef.current?.scrollToOffset({ 94 + animated: IS_NATIVE, 95 offset: -headerOffset, 96 }) 97 setMinimalShellMode(false) ··· 136 }) 137 }, [scrollToTop, feed, queryClient]) 138 139 + const shouldPrefetch = IS_NATIVE && isPageAdjacent 140 const isDiscoverFeed = feedInfo.uri === DISCOVER_FEED_URI 141 return ( 142 <View
+3 -3
src/view/com/feeds/MissingFeed.tsx
··· 4 import {useLingui} from '@lingui/react' 5 6 import {cleanError} from '#/lib/strings/errors' 7 - import {isNative, isWeb} from '#/platform/detection' 8 import {useModerationOpts} from '#/state/preferences/moderation-opts' 9 import {getFeedTypeFromUri} from '#/state/queries/feed' 10 import {useProfileQuery} from '#/state/queries/profile' ··· 15 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 16 import * as ProfileCard from '#/components/ProfileCard' 17 import {Text} from '#/components/Typography' 18 19 export function MissingFeed({ 20 style, ··· 83 a.italic, 84 ]} 85 numberOfLines={1}> 86 - {isWeb ? ( 87 <Trans>Click for information</Trans> 88 ) : ( 89 <Trans>Tap for information</Trans> ··· 205 </> 206 )} 207 </View> 208 - {isNative && ( 209 <Button 210 label={_(msg`Close`)} 211 onPress={() => control.close()}
··· 4 import {useLingui} from '@lingui/react' 5 6 import {cleanError} from '#/lib/strings/errors' 7 import {useModerationOpts} from '#/state/preferences/moderation-opts' 8 import {getFeedTypeFromUri} from '#/state/queries/feed' 9 import {useProfileQuery} from '#/state/queries/profile' ··· 14 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 15 import * as ProfileCard from '#/components/ProfileCard' 16 import {Text} from '#/components/Typography' 17 + import {IS_NATIVE, IS_WEB} from '#/env' 18 19 export function MissingFeed({ 20 style, ··· 83 a.italic, 84 ]} 85 numberOfLines={1}> 86 + {IS_WEB ? ( 87 <Trans>Click for information</Trans> 88 ) : ( 89 <Trans>Tap for information</Trans> ··· 205 </> 206 )} 207 </View> 208 + {IS_NATIVE && ( 209 <Button 210 label={_(msg`Close`)} 211 onPress={() => control.close()}
+4 -4
src/view/com/feeds/ProfileFeedgens.tsx
··· 20 21 import {cleanError} from '#/lib/strings/errors' 22 import {logger} from '#/logger' 23 - import {isIOS, isNative, isWeb} from '#/platform/detection' 24 import {usePreferencesQuery} from '#/state/queries/preferences' 25 import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' 26 import {useSession} from '#/state/session' ··· 33 import * as FeedCard from '#/components/FeedCard' 34 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 35 import {ListFooter} from '#/components/Lists' 36 37 const LOADING = {_reactKey: '__loading__'} 38 const EMPTY = {_reactKey: '__empty__'} ··· 111 112 const onScrollToTop = useCallback(() => { 113 scrollElRef.current?.scrollToOffset({ 114 - animated: isNative, 115 offset: -headerOffset, 116 }) 117 queryClient.invalidateQueries({queryKey: RQKEY(did)}) ··· 194 return ( 195 <View 196 style={[ 197 - (index !== 0 || isWeb) && a.border_t, 198 t.atoms.border_contrast_low, 199 a.px_lg, 200 a.py_lg, ··· 218 ) 219 220 useEffect(() => { 221 - if (isIOS && enabled && scrollElRef.current) { 222 const nativeTag = findNodeHandle(scrollElRef.current) 223 setScrollViewTag(nativeTag) 224 }
··· 20 21 import {cleanError} from '#/lib/strings/errors' 22 import {logger} from '#/logger' 23 import {usePreferencesQuery} from '#/state/queries/preferences' 24 import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' 25 import {useSession} from '#/state/session' ··· 32 import * as FeedCard from '#/components/FeedCard' 33 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 34 import {ListFooter} from '#/components/Lists' 35 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 36 37 const LOADING = {_reactKey: '__loading__'} 38 const EMPTY = {_reactKey: '__empty__'} ··· 111 112 const onScrollToTop = useCallback(() => { 113 scrollElRef.current?.scrollToOffset({ 114 + animated: IS_NATIVE, 115 offset: -headerOffset, 116 }) 117 queryClient.invalidateQueries({queryKey: RQKEY(did)}) ··· 194 return ( 195 <View 196 style={[ 197 + (index !== 0 || IS_WEB) && a.border_t, 198 t.atoms.border_contrast_low, 199 a.px_lg, 200 a.py_lg, ··· 218 ) 219 220 useEffect(() => { 221 + if (IS_IOS && enabled && scrollElRef.current) { 222 const nativeTag = findNodeHandle(scrollElRef.current) 223 setScrollViewTag(nativeTag) 224 }
+5 -5
src/view/com/lightbox/ImageViewing/index.tsx
··· 41 42 import {type Dimensions} from '#/lib/media/types' 43 import {colors, s} from '#/lib/styles' 44 - import {isIOS} from '#/platform/detection' 45 import {type Lightbox} from '#/state/lightbox' 46 import {Button} from '#/view/com/util/forms/Button' 47 import {Text} from '#/view/com/util/text/Text' 48 import {ScrollView} from '#/view/com/util/Views' 49 import {useTheme} from '#/alf' 50 import {setSystemUITheme} from '#/alf/util/systemUI' 51 import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' 52 import {type ImageSource, type Transform} from './@types' 53 import ImageDefaultHeader from './components/ImageDefaultHeader' ··· 59 const PIXEL_RATIO = PixelRatio.get() 60 61 const SLOW_SPRING: WithSpringConfig = { 62 - mass: isIOS ? 1.25 : 0.75, 63 damping: 300, 64 stiffness: 800, 65 restDisplacementThreshold: 0.01, 66 } 67 const FAST_SPRING: WithSpringConfig = { 68 - mass: isIOS ? 1.25 : 0.75, 69 damping: 150, 70 stiffness: 900, 71 restDisplacementThreshold: 0.01, ··· 248 ) 249 opacity -= dragProgress 250 } 251 - const factor = isIOS ? 100 : 50 252 return { 253 opacity: Math.round(opacity * factor) / factor, 254 } ··· 686 maxHeight: '100%', 687 }, 688 footerText: { 689 - paddingBottom: isIOS ? 20 : 16, 690 }, 691 footerBtns: { 692 flexDirection: 'row',
··· 41 42 import {type Dimensions} from '#/lib/media/types' 43 import {colors, s} from '#/lib/styles' 44 import {type Lightbox} from '#/state/lightbox' 45 import {Button} from '#/view/com/util/forms/Button' 46 import {Text} from '#/view/com/util/text/Text' 47 import {ScrollView} from '#/view/com/util/Views' 48 import {useTheme} from '#/alf' 49 import {setSystemUITheme} from '#/alf/util/systemUI' 50 + import {IS_IOS} from '#/env' 51 import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' 52 import {type ImageSource, type Transform} from './@types' 53 import ImageDefaultHeader from './components/ImageDefaultHeader' ··· 59 const PIXEL_RATIO = PixelRatio.get() 60 61 const SLOW_SPRING: WithSpringConfig = { 62 + mass: IS_IOS ? 1.25 : 0.75, 63 damping: 300, 64 stiffness: 800, 65 restDisplacementThreshold: 0.01, 66 } 67 const FAST_SPRING: WithSpringConfig = { 68 + mass: IS_IOS ? 1.25 : 0.75, 69 damping: 150, 70 stiffness: 900, 71 restDisplacementThreshold: 0.01, ··· 248 ) 249 opacity -= dragProgress 250 } 251 + const factor = IS_IOS ? 100 : 50 252 return { 253 opacity: Math.round(opacity * factor) / factor, 254 } ··· 686 maxHeight: '100%', 687 }, 688 footerText: { 689 + paddingBottom: IS_IOS ? 20 : 16, 690 }, 691 footerBtns: { 692 flexDirection: 'row',
+4 -4
src/view/com/lists/ProfileLists.tsx
··· 20 21 import {cleanError} from '#/lib/strings/errors' 22 import {logger} from '#/logger' 23 - import {isIOS, isNative, isWeb} from '#/platform/detection' 24 import {usePreferencesQuery} from '#/state/queries/preferences' 25 import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' 26 import {useSession} from '#/state/session' ··· 33 import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 34 import * as ListCard from '#/components/ListCard' 35 import {ListFooter} from '#/components/Lists' 36 37 const LOADING = {_reactKey: '__loading__'} 38 const EMPTY = {_reactKey: '__empty__'} ··· 111 112 const onScrollToTop = useCallback(() => { 113 scrollElRef.current?.scrollToOffset({ 114 - animated: isNative, 115 offset: -headerOffset, 116 }) 117 queryClient.invalidateQueries({queryKey: RQKEY(did)}) ··· 193 return ( 194 <View 195 style={[ 196 - (index !== 0 || isWeb) && a.border_t, 197 t.atoms.border_contrast_low, 198 a.px_lg, 199 a.py_lg, ··· 217 ) 218 219 useEffect(() => { 220 - if (isIOS && enabled && scrollElRef.current) { 221 const nativeTag = findNodeHandle(scrollElRef.current) 222 setScrollViewTag(nativeTag) 223 }
··· 20 21 import {cleanError} from '#/lib/strings/errors' 22 import {logger} from '#/logger' 23 import {usePreferencesQuery} from '#/state/queries/preferences' 24 import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' 25 import {useSession} from '#/state/session' ··· 32 import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 33 import * as ListCard from '#/components/ListCard' 34 import {ListFooter} from '#/components/Lists' 35 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 36 37 const LOADING = {_reactKey: '__loading__'} 38 const EMPTY = {_reactKey: '__empty__'} ··· 111 112 const onScrollToTop = useCallback(() => { 113 scrollElRef.current?.scrollToOffset({ 114 + animated: IS_NATIVE, 115 offset: -headerOffset, 116 }) 117 queryClient.invalidateQueries({queryKey: RQKEY(did)}) ··· 193 return ( 194 <View 195 style={[ 196 + (index !== 0 || IS_WEB) && a.border_t, 197 t.atoms.border_contrast_low, 198 a.px_lg, 199 a.py_lg, ··· 217 ) 218 219 useEffect(() => { 220 + if (IS_IOS && enabled && scrollElRef.current) { 221 const nativeTag = findNodeHandle(scrollElRef.current) 222 setScrollViewTag(nativeTag) 223 }
+3 -3
src/view/com/modals/DeleteAccount.tsx
··· 16 import {cleanError} from '#/lib/strings/errors' 17 import {colors, gradients, s} from '#/lib/styles' 18 import {useTheme} from '#/lib/ThemeContext' 19 - import {isAndroid, isWeb} from '#/platform/detection' 20 import {useModalControls} from '#/state/modals' 21 import {useAgent, useSession, useSessionApi} from '#/state/session' 22 import {pdsAgent} from '#/state/session/agent' 23 import {atoms as a, useTheme as useNewTheme} from '#/alf' 24 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 25 import {Text as NewText} from '#/components/Typography' 26 import {resetToTab} from '../../../Navigation' 27 import {ErrorMessage} from '../util/error/ErrorMessage' 28 import {Text} from '../util/text/Text' 29 import * as Toast from '../util/Toast' 30 import {ScrollView, TextInput} from './util' 31 32 - export const snapPoints = isAndroid ? ['90%'] : ['55%'] 33 34 export function Component({}: {}) { 35 const pal = usePalette('default') ··· 174 </> 175 )} 176 177 - <View style={[!isWeb && a.px_xl]}> 178 <View 179 style={[ 180 a.w_full,
··· 16 import {cleanError} from '#/lib/strings/errors' 17 import {colors, gradients, s} from '#/lib/styles' 18 import {useTheme} from '#/lib/ThemeContext' 19 import {useModalControls} from '#/state/modals' 20 import {useAgent, useSession, useSessionApi} from '#/state/session' 21 import {pdsAgent} from '#/state/session/agent' 22 import {atoms as a, useTheme as useNewTheme} from '#/alf' 23 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 24 import {Text as NewText} from '#/components/Typography' 25 + import {IS_ANDROID, IS_WEB} from '#/env' 26 import {resetToTab} from '../../../Navigation' 27 import {ErrorMessage} from '../util/error/ErrorMessage' 28 import {Text} from '../util/text/Text' 29 import * as Toast from '../util/Toast' 30 import {ScrollView, TextInput} from './util' 31 32 + export const snapPoints = IS_ANDROID ? ['90%'] : ['55%'] 33 34 export function Component({}: {}) { 35 const pal = usePalette('default') ··· 174 </> 175 )} 176 177 + <View style={[!IS_WEB && a.px_xl]}> 178 <View 179 style={[ 180 a.w_full,
+6 -6
src/view/com/modals/UserAddRemoveLists.tsx
··· 14 import {cleanError} from '#/lib/strings/errors' 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 import {s} from '#/lib/styles' 17 - import {useTheme} from '#/lib/ThemeContext' 18 - import {isAndroid, isMobileWeb, isWeb} from '#/platform/detection' 19 import {useModalControls} from '#/state/modals' 20 import { 21 getMembership, ··· 25 useListMembershipRemoveMutation, 26 } from '#/state/queries/list-memberships' 27 import {useSession} from '#/state/session' 28 import {MyLists} from '../lists/MyLists' 29 import {Button} from '../util/forms/Button' 30 import {Text} from '../util/text/Text' ··· 57 }, [closeModal]) 58 59 const listStyle = React.useMemo(() => { 60 - if (isMobileWeb) { 61 return [pal.border, {height: screenHeight / 2}] 62 - } else if (isWeb) { 63 return [pal.border, {height: screenHeight / 1.5}] 64 } 65 ··· 245 246 const styles = StyleSheet.create({ 247 container: { 248 - paddingHorizontal: isWeb ? 0 : 16, 249 }, 250 btns: { 251 position: 'relative', ··· 254 justifyContent: 'center', 255 gap: 10, 256 paddingTop: 10, 257 - paddingBottom: isAndroid ? 10 : 0, 258 borderTopWidth: StyleSheet.hairlineWidth, 259 }, 260 footerBtn: {
··· 14 import {cleanError} from '#/lib/strings/errors' 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 import {s} from '#/lib/styles' 17 + import {useTheme} from '#/alf' 18 import {useModalControls} from '#/state/modals' 19 import { 20 getMembership, ··· 24 useListMembershipRemoveMutation, 25 } from '#/state/queries/list-memberships' 26 import {useSession} from '#/state/session' 27 + import {IS_ANDROID, IS_WEB, IS_WEB_MOBILE} from '#/env' 28 import {MyLists} from '../lists/MyLists' 29 import {Button} from '../util/forms/Button' 30 import {Text} from '../util/text/Text' ··· 57 }, [closeModal]) 58 59 const listStyle = React.useMemo(() => { 60 + if (IS_WEB_MOBILE) { 61 return [pal.border, {height: screenHeight / 2}] 62 + } else if (IS_WEB) { 63 return [pal.border, {height: screenHeight / 1.5}] 64 } 65 ··· 245 246 const styles = StyleSheet.create({ 247 container: { 248 + paddingHorizontal: IS_WEB ? 0 : 16, 249 }, 250 btns: { 251 position: 'relative', ··· 254 justifyContent: 'center', 255 gap: 10, 256 paddingTop: 10, 257 + paddingBottom: IS_ANDROID ? 10 : 0, 258 borderTopWidth: StyleSheet.hairlineWidth, 259 }, 260 footerBtn: {
+2 -2
src/view/com/pager/PagerHeaderContext.tsx
··· 1 import React, {useContext} from 'react' 2 import {type SharedValue} from 'react-native-reanimated' 3 4 - import {isNative} from '#/platform/detection' 5 6 export const PagerHeaderContext = React.createContext<{ 7 scrollY: SharedValue<number> ··· 37 38 export function usePagerHeaderContext() { 39 const ctx = useContext(PagerHeaderContext) 40 - if (isNative) { 41 if (!ctx) { 42 throw new Error( 43 'usePagerHeaderContext must be used within a HeaderProvider',
··· 1 import React, {useContext} from 'react' 2 import {type SharedValue} from 'react-native-reanimated' 3 4 + import {IS_NATIVE} from '#/env' 5 6 export const PagerHeaderContext = React.createContext<{ 7 scrollY: SharedValue<number> ··· 37 38 export function usePagerHeaderContext() { 39 const ctx = useContext(PagerHeaderContext) 40 + if (IS_NATIVE) { 41 if (!ctx) { 42 throw new Error( 43 'usePagerHeaderContext must be used within a HeaderProvider',
+3 -3
src/view/com/pager/PagerWithHeader.tsx
··· 18 19 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 20 import {ScrollProvider} from '#/lib/ScrollContext' 21 - import {isIOS} from '#/platform/detection' 22 import { 23 Pager, 24 type PagerRef, 25 type RenderTabBarFnProps, 26 } from '#/view/com/pager/Pager' 27 import {useTheme} from '#/alf' 28 import {type ListMethods} from '../util/List' 29 import {PagerHeaderProvider} from './PagerHeaderContext' 30 import {TabBar} from './TabBar' ··· 273 const headerRef = useRef(null) 274 return ( 275 <Animated.View 276 - pointerEvents={isIOS ? 'auto' : 'box-none'} 277 style={[styles.tabBarMobile, headerTransform, t.atoms.bg]}> 278 <View 279 ref={headerRef} 280 - pointerEvents={isIOS ? 'auto' : 'box-none'} 281 collapsable={false}> 282 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} 283 {
··· 18 19 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 20 import {ScrollProvider} from '#/lib/ScrollContext' 21 import { 22 Pager, 23 type PagerRef, 24 type RenderTabBarFnProps, 25 } from '#/view/com/pager/Pager' 26 import {useTheme} from '#/alf' 27 + import {IS_IOS} from '#/env' 28 import {type ListMethods} from '../util/List' 29 import {PagerHeaderProvider} from './PagerHeaderContext' 30 import {TabBar} from './TabBar' ··· 273 const headerRef = useRef(null) 274 return ( 275 <Animated.View 276 + pointerEvents={IS_IOS ? 'auto' : 'box-none'} 277 style={[styles.tabBarMobile, headerTransform, t.atoms.bg]}> 278 <View 279 ref={headerRef} 280 + pointerEvents={IS_IOS ? 'auto' : 'box-none'} 281 collapsable={false}> 282 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} 283 {
+2 -2
src/view/com/posts/CustomFeedEmptyState.tsx
··· 13 import {type NavigationProp} from '#/lib/routes/types' 14 import {s} from '#/lib/styles' 15 import {logger} from '#/logger' 16 - import {isWeb} from '#/platform/detection' 17 import {useFeedFeedbackContext} from '#/state/feed-feedback' 18 import {useSession} from '#/state/session' 19 import {Button} from '../util/forms/Button' 20 import {Text} from '../util/text/Text' 21 ··· 44 const navigation = useNavigation<NavigationProp>() 45 46 const onPressFindAccounts = React.useCallback(() => { 47 - if (isWeb) { 48 navigation.navigate('Search', {}) 49 } else { 50 navigation.navigate('SearchTab')
··· 13 import {type NavigationProp} from '#/lib/routes/types' 14 import {s} from '#/lib/styles' 15 import {logger} from '#/logger' 16 import {useFeedFeedbackContext} from '#/state/feed-feedback' 17 import {useSession} from '#/state/session' 18 + import {IS_WEB} from '#/env' 19 import {Button} from '../util/forms/Button' 20 import {Text} from '../util/text/Text' 21 ··· 44 const navigation = useNavigation<NavigationProp>() 45 46 const onPressFindAccounts = React.useCallback(() => { 47 + if (IS_WEB) { 48 navigation.navigate('Search', {}) 49 } else { 50 navigation.navigate('SearchTab')
+2 -2
src/view/com/posts/FollowingEmptyState.tsx
··· 11 import {MagnifyingGlassIcon} from '#/lib/icons' 12 import {type NavigationProp} from '#/lib/routes/types' 13 import {s} from '#/lib/styles' 14 - import {isWeb} from '#/platform/detection' 15 import {Button} from '../util/forms/Button' 16 import {Text} from '../util/text/Text' 17 ··· 21 const navigation = useNavigation<NavigationProp>() 22 23 const onPressFindAccounts = React.useCallback(() => { 24 - if (isWeb) { 25 navigation.navigate('Search', {}) 26 } else { 27 navigation.navigate('SearchTab')
··· 11 import {MagnifyingGlassIcon} from '#/lib/icons' 12 import {type NavigationProp} from '#/lib/routes/types' 13 import {s} from '#/lib/styles' 14 + import {IS_WEB} from '#/env' 15 import {Button} from '../util/forms/Button' 16 import {Text} from '../util/text/Text' 17 ··· 21 const navigation = useNavigation<NavigationProp>() 22 23 const onPressFindAccounts = React.useCallback(() => { 24 + if (IS_WEB) { 25 navigation.navigate('Search', {}) 26 } else { 27 navigation.navigate('SearchTab')
+2 -2
src/view/com/posts/FollowingEndOfFeed.tsx
··· 10 import {usePalette} from '#/lib/hooks/usePalette' 11 import {type NavigationProp} from '#/lib/routes/types' 12 import {s} from '#/lib/styles' 13 - import {isWeb} from '#/platform/detection' 14 import {Button} from '../util/forms/Button' 15 import {Text} from '../util/text/Text' 16 ··· 20 const navigation = useNavigation<NavigationProp>() 21 22 const onPressFindAccounts = React.useCallback(() => { 23 - if (isWeb) { 24 navigation.navigate('Search', {}) 25 } else { 26 navigation.navigate('SearchTab')
··· 10 import {usePalette} from '#/lib/hooks/usePalette' 11 import {type NavigationProp} from '#/lib/routes/types' 12 import {s} from '#/lib/styles' 13 + import {IS_WEB} from '#/env' 14 import {Button} from '../util/forms/Button' 15 import {Text} from '../util/text/Text' 16 ··· 20 const navigation = useNavigation<NavigationProp>() 21 22 const onPressFindAccounts = React.useCallback(() => { 23 + if (IS_WEB) { 24 navigation.navigate('Search', {}) 25 } else { 26 navigation.navigate('SearchTab')
+4 -5
src/view/com/posts/PostFeed.tsx
··· 34 import {logEvent, useGate} from '#/lib/statsig/statsig' 35 import {isNetworkError} from '#/lib/strings/errors' 36 import {logger} from '#/logger' 37 - import {isIOS, isNative, isWeb} from '#/platform/detection' 38 import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow' 39 import {listenPostCreated} from '#/state/events' 40 import {useFeedFeedbackContext} from '#/state/feed-feedback' ··· 72 } from '#/components/feeds/PostFeedVideoGridRow' 73 import {TrendingInterstitial} from '#/components/interstitials/Trending' 74 import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos' 75 import {DiscoverFeedLiveEventFeedsAndTrendingBanner} from '#/features/liveEvents/components/DiscoverFeedLiveEventFeedsAndTrendingBanner' 76 import {ComposerPrompt} from '../feeds/ComposerPrompt' 77 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' ··· 314 const [feedType, feedUriOrActorDid, feedTab] = feed.split('|') 315 const {gtMobile} = useBreakpoints() 316 const {rightNavVisible} = useLayoutBreakpoints() 317 - const areVideoFeedsEnabled = isNative 318 319 const [hasPressedShowLessUris, setHasPressedShowLessUris] = useState( 320 () => new Set<string>(), ··· 514 for (const page of data.pages) { 515 for (const slice of page.slices) { 516 const item = slice.items.find( 517 - // eslint-disable-next-line @typescript-eslint/no-shadow 518 item => item.uri === slice.feedPostUri, 519 ) 520 if ( ··· 986 * reach the end, so that content isn't cut off by the bottom of the 987 * screen. 988 */ 989 - const offset = Math.max(headerOffset, 32) * (isWeb ? 1 : 2) 990 991 return isFetchingNextPage ? ( 992 <View style={[styles.feedFooter]}> ··· 1137 } 1138 initialNumToRender={initialNumToRenderOverride ?? initialNumToRender} 1139 windowSize={9} 1140 - maxToRenderPerBatch={isIOS ? 5 : 1} 1141 updateCellsBatchingPeriod={40} 1142 onItemSeen={onItemSeen} 1143 />
··· 34 import {logEvent, useGate} from '#/lib/statsig/statsig' 35 import {isNetworkError} from '#/lib/strings/errors' 36 import {logger} from '#/logger' 37 import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow' 38 import {listenPostCreated} from '#/state/events' 39 import {useFeedFeedbackContext} from '#/state/feed-feedback' ··· 71 } from '#/components/feeds/PostFeedVideoGridRow' 72 import {TrendingInterstitial} from '#/components/interstitials/Trending' 73 import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos' 74 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 75 import {DiscoverFeedLiveEventFeedsAndTrendingBanner} from '#/features/liveEvents/components/DiscoverFeedLiveEventFeedsAndTrendingBanner' 76 import {ComposerPrompt} from '../feeds/ComposerPrompt' 77 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' ··· 314 const [feedType, feedUriOrActorDid, feedTab] = feed.split('|') 315 const {gtMobile} = useBreakpoints() 316 const {rightNavVisible} = useLayoutBreakpoints() 317 + const areVideoFeedsEnabled = IS_NATIVE 318 319 const [hasPressedShowLessUris, setHasPressedShowLessUris] = useState( 320 () => new Set<string>(), ··· 514 for (const page of data.pages) { 515 for (const slice of page.slices) { 516 const item = slice.items.find( 517 item => item.uri === slice.feedPostUri, 518 ) 519 if ( ··· 985 * reach the end, so that content isn't cut off by the bottom of the 986 * screen. 987 */ 988 + const offset = Math.max(headerOffset, 32) * (IS_WEB ? 1 : 2) 989 990 return isFetchingNextPage ? ( 991 <View style={[styles.feedFooter]}> ··· 1136 } 1137 initialNumToRender={initialNumToRenderOverride ?? initialNumToRender} 1138 windowSize={9} 1139 + maxToRenderPerBatch={IS_IOS ? 5 : 1} 1140 updateCellsBatchingPeriod={40} 1141 onItemSeen={onItemSeen} 1142 />
+2 -2
src/view/com/profile/ProfileFollows.tsx
··· 8 import {type NavigationProp} from '#/lib/routes/types' 9 import {cleanError} from '#/lib/strings/errors' 10 import {logger} from '#/logger' 11 - import {isWeb} from '#/platform/detection' 12 import {useProfileFollowsQuery} from '#/state/queries/profile-follows' 13 import {useResolveDidQuery} from '#/state/queries/resolve-uri' 14 import {useSession} from '#/state/session' 15 import {FindContactsBannerNUX} from '#/components/contacts/FindContactsBannerNUX' 16 import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2' 17 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 18 import {List} from '../util/List' 19 import {ProfileCardWithFollowBtn} from './ProfileCard' 20 ··· 49 const navigation = useNavigation<NavigationProp>() 50 51 const onPressFindAccounts = React.useCallback(() => { 52 - if (isWeb) { 53 navigation.navigate('Search', {}) 54 } else { 55 navigation.navigate('SearchTab')
··· 8 import {type NavigationProp} from '#/lib/routes/types' 9 import {cleanError} from '#/lib/strings/errors' 10 import {logger} from '#/logger' 11 import {useProfileFollowsQuery} from '#/state/queries/profile-follows' 12 import {useResolveDidQuery} from '#/state/queries/resolve-uri' 13 import {useSession} from '#/state/session' 14 import {FindContactsBannerNUX} from '#/components/contacts/FindContactsBannerNUX' 15 import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2' 16 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 17 + import {IS_WEB} from '#/env' 18 import {List} from '../util/List' 19 import {ProfileCardWithFollowBtn} from './ProfileCard' 20 ··· 49 const navigation = useNavigation<NavigationProp>() 50 51 const onPressFindAccounts = React.useCallback(() => { 52 + if (IS_WEB) { 53 navigation.navigate('Search', {}) 54 } else { 55 navigation.navigate('SearchTab')
+10 -8
src/view/com/profile/ProfileMenu.tsx
··· 12 import {shareText, shareUrl} from '#/lib/sharing' 13 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 14 import {logger} from '#/logger' 15 - import {isWeb} from '#/platform/detection' 16 import {type Shadow} from '#/state/cache/types' 17 import {useModalControls} from '#/state/modals' 18 import { ··· 67 import {useFullVerificationState} from '#/components/verification' 68 import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' 69 import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' 70 import {Dot} from '#/features/nuxs/components/Dot' 71 import {Gradient} from '#/features/nuxs/components/Gradient' 72 import {useDevMode} from '#/storage/hooks/dev-mode' ··· 283 <Menu.Item 284 testID="profileHeaderDropdownShareBtn" 285 label={ 286 - isWeb ? _(msg`Copy link to profile`) : _(msg`Share via...`) 287 } 288 onPress={() => { 289 if (showLoggedOutWarning) { ··· 293 } 294 }}> 295 <Menu.ItemText> 296 - {isWeb ? ( 297 <Trans>Copy link to profile</Trans> 298 ) : ( 299 <Trans>Share via...</Trans> 300 )} 301 </Menu.ItemText> 302 - <Menu.ItemIcon icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} /> 303 </Menu.Item> 304 <Menu.Item 305 testID="profileHeaderDropdownShareBtn" 306 label={ 307 - isWeb 308 ? _(msg`Copy via bsky.app`) 309 : _(msg`Share via bsky.app...`) 310 } ··· 316 } 317 }}> 318 <Menu.ItemText> 319 - {isWeb ? ( 320 <Trans>Copy via bsky.app</Trans> 321 ) : ( 322 <Trans>Share via bsky.app...</Trans> 323 )} 324 </Menu.ItemText> 325 - <Menu.ItemIcon icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} /> 326 </Menu.Item> 327 <Menu.Item 328 testID="profileHeaderDropdownSearchBtn" ··· 453 a.flex_0, 454 { 455 color: t.palette.primary_500, 456 - right: isWeb ? -8 : -4, 457 }, 458 ]}> 459 <Trans>New</Trans>
··· 12 import {shareText, shareUrl} from '#/lib/sharing' 13 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 14 import {logger} from '#/logger' 15 import {type Shadow} from '#/state/cache/types' 16 import {useModalControls} from '#/state/modals' 17 import { ··· 66 import {useFullVerificationState} from '#/components/verification' 67 import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' 68 import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' 69 + import {IS_WEB} from '#/env' 70 import {Dot} from '#/features/nuxs/components/Dot' 71 import {Gradient} from '#/features/nuxs/components/Gradient' 72 import {useDevMode} from '#/storage/hooks/dev-mode' ··· 283 <Menu.Item 284 testID="profileHeaderDropdownShareBtn" 285 label={ 286 + IS_WEB ? _(msg`Copy link to profile`) : _(msg`Share via...`) 287 } 288 onPress={() => { 289 if (showLoggedOutWarning) { ··· 293 } 294 }}> 295 <Menu.ItemText> 296 + {IS_WEB ? ( 297 <Trans>Copy link to profile</Trans> 298 ) : ( 299 <Trans>Share via...</Trans> 300 )} 301 </Menu.ItemText> 302 + <Menu.ItemIcon 303 + icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon} 304 + /> 305 </Menu.Item> 306 <Menu.Item 307 testID="profileHeaderDropdownShareBtn" 308 label={ 309 + IS_WEB 310 ? _(msg`Copy via bsky.app`) 311 : _(msg`Share via bsky.app...`) 312 } ··· 318 } 319 }}> 320 <Menu.ItemText> 321 + {IS_WEB ? ( 322 <Trans>Copy via bsky.app</Trans> 323 ) : ( 324 <Trans>Share via bsky.app...</Trans> 325 )} 326 </Menu.ItemText> 327 + <Menu.ItemIcon icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon} /> 328 </Menu.Item> 329 <Menu.Item 330 testID="profileHeaderDropdownSearchBtn" ··· 455 a.flex_0, 456 { 457 color: t.palette.primary_500, 458 + right: IS_WEB ? -8 : -4, 459 }, 460 ]}> 461 <Trans>New</Trans>
+4 -2
src/view/com/testing/TestCtrls.e2e.tsx
··· 1 import {useState} from 'react' 2 - import {LogBox, Pressable, View, TextInput} from 'react-native' 3 import {useQueryClient} from '@tanstack/react-query' 4 5 import {BLUESKY_PROXY_HEADER} from '#/lib/constants' 6 - import {useSessionApi, useAgent} from '#/state/session' 7 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 8 import {useOnboardingDispatch} from '#/state/shell/onboarding' 9 import {navigate} from '../../../Navigation' ··· 50 return ( 51 <View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}> 52 <TextInput 53 testID="e2eProxyHeaderInput" 54 onChangeText={val => setProxyHeader(val as any)} 55 onSubmitEditing={() => {
··· 1 import {useState} from 'react' 2 + import {LogBox, Pressable, TextInput, View} from 'react-native' 3 import {useQueryClient} from '@tanstack/react-query' 4 5 import {BLUESKY_PROXY_HEADER} from '#/lib/constants' 6 + import {useAgent, useSessionApi} from '#/state/session' 7 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 8 import {useOnboardingDispatch} from '#/state/shell/onboarding' 9 import {navigate} from '../../../Navigation' ··· 50 return ( 51 <View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}> 52 <TextInput 53 + accessibilityLabel="Text input field" 54 + accessibilityHint="Enter proxy header" 55 testID="e2eProxyHeaderInput" 56 onChangeText={val => setProxyHeader(val as any)} 57 onSubmitEditing={() => {
-2
src/view/com/util/Alert.web.tsx
··· 3 class WebAlert implements Pick<AlertStatic, 'alert'> { 4 public alert(title: string, message?: string, buttons?: AlertButton[]): void { 5 if (buttons === undefined || buttons.length === 0) { 6 - // eslint-disable-next-line no-alert 7 window.alert([title, message].filter(Boolean).join('\n')) 8 return 9 } 10 11 - // eslint-disable-next-line no-alert 12 const result = window.confirm([title, message].filter(Boolean).join('\n')) 13 14 if (result === true) {
··· 3 class WebAlert implements Pick<AlertStatic, 'alert'> { 4 public alert(title: string, message?: string, buttons?: AlertButton[]): void { 5 if (buttons === undefined || buttons.length === 0) { 6 window.alert([title, message].filter(Boolean).join('\n')) 7 return 8 } 9 10 const result = window.confirm([title, message].filter(Boolean).join('\n')) 11 12 if (result === true) {
+4 -4
src/view/com/util/Link.tsx
··· 25 linkRequiresWarning, 26 } from '#/lib/strings/url-helpers' 27 import {type TypographyVariant} from '#/lib/ThemeContext' 28 - import {isAndroid, isWeb} from '#/platform/detection' 29 import {emitSoftReset} from '#/state/events' 30 import {useModalControls} from '#/state/modals' 31 import {WebAuxClickWrapper} from '#/view/com/util/WebAuxClickWrapper' 32 import {useTheme} from '#/alf' 33 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 34 import {router} from '../../../routes' 35 import {PressableWithHover} from './PressableWithHover' 36 import {Text} from './text/Text' ··· 130 android_ripple={{ 131 color: t.atoms.bg_contrast_25.backgroundColor, 132 }} 133 - unstable_pressDelay={isAndroid ? 90 : undefined}> 134 {/* @ts-ignore web only -prf */} 135 <View style={style} href={anchorHref}> 136 {children ? children : <Text>{title || 'link'}</Text>} ··· 219 }) 220 } 221 if ( 222 - isWeb && 223 href !== '#' && 224 e != null && 225 isModifiedEvent(e as React.MouseEvent) ··· 323 onBeforePress, 324 ...props 325 }: TextLinkOnWebOnlyProps) { 326 - if (isWeb) { 327 return ( 328 <TextLink 329 testID={testID}
··· 25 linkRequiresWarning, 26 } from '#/lib/strings/url-helpers' 27 import {type TypographyVariant} from '#/lib/ThemeContext' 28 import {emitSoftReset} from '#/state/events' 29 import {useModalControls} from '#/state/modals' 30 import {WebAuxClickWrapper} from '#/view/com/util/WebAuxClickWrapper' 31 import {useTheme} from '#/alf' 32 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 33 + import {IS_ANDROID, IS_WEB} from '#/env' 34 import {router} from '../../../routes' 35 import {PressableWithHover} from './PressableWithHover' 36 import {Text} from './text/Text' ··· 130 android_ripple={{ 131 color: t.atoms.bg_contrast_25.backgroundColor, 132 }} 133 + unstable_pressDelay={IS_ANDROID ? 90 : undefined}> 134 {/* @ts-ignore web only -prf */} 135 <View style={style} href={anchorHref}> 136 {children ? children : <Text>{title || 'link'}</Text>} ··· 219 }) 220 } 221 if ( 222 + IS_WEB && 223 href !== '#' && 224 e != null && 225 isModifiedEvent(e as React.MouseEvent) ··· 323 onBeforePress, 324 ...props 325 }: TextLinkOnWebOnlyProps) { 326 + if (IS_WEB) { 327 return ( 328 <TextLink 329 testID={testID}
+3 -3
src/view/com/util/List.tsx
··· 11 import {useDedupe} from '#/lib/hooks/useDedupe' 12 import {useScrollHandlers} from '#/lib/ScrollContext' 13 import {addStyle} from '#/lib/styles' 14 - import {isIOS} from '#/platform/detection' 15 import {useLightbox} from '#/state/lightbox' 16 import {useTheme} from '#/alf' 17 import {FlatList_INTERNAL} from './Views' 18 19 export type ListMethods = FlatList_INTERNAL ··· 94 } 95 } 96 97 - if (isIOS) { 98 runOnJS(dedupe)(updateActiveVideoViewAsync) 99 } 100 }, ··· 184 185 // We only want to use this context value on iOS because the `scrollsToTop` prop is iOS-only 186 // removing it saves us a re-render on Android 187 - const useAllowScrollToTop = isIOS ? useAllowScrollToTopIOS : () => undefined 188 function useAllowScrollToTopIOS() { 189 const {activeLightbox} = useLightbox() 190 return !activeLightbox
··· 11 import {useDedupe} from '#/lib/hooks/useDedupe' 12 import {useScrollHandlers} from '#/lib/ScrollContext' 13 import {addStyle} from '#/lib/styles' 14 import {useLightbox} from '#/state/lightbox' 15 import {useTheme} from '#/alf' 16 + import {IS_IOS} from '#/env' 17 import {FlatList_INTERNAL} from './Views' 18 19 export type ListMethods = FlatList_INTERNAL ··· 94 } 95 } 96 97 + if (IS_IOS) { 98 runOnJS(dedupe)(updateActiveVideoViewAsync) 99 } 100 }, ··· 184 185 // We only want to use this context value on iOS because the `scrollsToTop` prop is iOS-only 186 // removing it saves us a re-render on Android 187 + const useAllowScrollToTop = IS_IOS ? useAllowScrollToTopIOS : () => undefined 188 function useAllowScrollToTopIOS() { 189 const {activeLightbox} = useLightbox() 190 return !activeLightbox
+8 -8
src/view/com/util/MainScrollProvider.tsx
··· 9 import EventEmitter from 'eventemitter3' 10 11 import {ScrollProvider} from '#/lib/ScrollContext' 12 - import {isNative, isWeb} from '#/platform/detection' 13 import {useMinimalShellMode} from '#/state/shell' 14 import {useShellLayout} from '#/state/shell/shell-layout' 15 16 const WEB_HIDE_SHELL_THRESHOLD = 200 17 ··· 35 ) 36 37 useEffect(() => { 38 - if (isWeb) { 39 return listenToForcedWindowScroll(() => { 40 startDragOffset.set(null) 41 startMode.set(null) ··· 48 (e: NativeScrollEvent) => { 49 'worklet' 50 const offsetY = Math.max(0, e.contentOffset.y) 51 - if (isNative) { 52 const startDragOffsetValue = startDragOffset.get() 53 if (startDragOffsetValue === null) { 54 return ··· 75 (e: NativeScrollEvent) => { 76 'worklet' 77 const offsetY = Math.max(0, e.contentOffset.y) 78 - if (isNative) { 79 startDragOffset.set(offsetY) 80 startMode.set(headerMode.get()) 81 } ··· 86 const onEndDrag = useCallback( 87 (e: NativeScrollEvent) => { 88 'worklet' 89 - if (isNative) { 90 if (e.velocity && e.velocity.y !== 0) { 91 // If we detect a velocity, wait for onMomentumEnd to snap. 92 return ··· 100 const onMomentumEnd = useCallback( 101 (e: NativeScrollEvent) => { 102 'worklet' 103 - if (isNative) { 104 snapToClosestState(e) 105 } 106 }, ··· 111 (e: NativeScrollEvent) => { 112 'worklet' 113 const offsetY = Math.max(0, e.contentOffset.y) 114 - if (isNative) { 115 const startDragOffsetValue = startDragOffset.get() 116 const startModeValue = startMode.get() 117 if (startDragOffsetValue === null || startModeValue === null) { ··· 177 178 const emitter = new EventEmitter() 179 180 - if (isWeb) { 181 const originalScroll = window.scroll 182 window.scroll = function () { 183 emitter.emit('forced-scroll')
··· 9 import EventEmitter from 'eventemitter3' 10 11 import {ScrollProvider} from '#/lib/ScrollContext' 12 import {useMinimalShellMode} from '#/state/shell' 13 import {useShellLayout} from '#/state/shell/shell-layout' 14 + import {IS_NATIVE, IS_WEB} from '#/env' 15 16 const WEB_HIDE_SHELL_THRESHOLD = 200 17 ··· 35 ) 36 37 useEffect(() => { 38 + if (IS_WEB) { 39 return listenToForcedWindowScroll(() => { 40 startDragOffset.set(null) 41 startMode.set(null) ··· 48 (e: NativeScrollEvent) => { 49 'worklet' 50 const offsetY = Math.max(0, e.contentOffset.y) 51 + if (IS_NATIVE) { 52 const startDragOffsetValue = startDragOffset.get() 53 if (startDragOffsetValue === null) { 54 return ··· 75 (e: NativeScrollEvent) => { 76 'worklet' 77 const offsetY = Math.max(0, e.contentOffset.y) 78 + if (IS_NATIVE) { 79 startDragOffset.set(offsetY) 80 startMode.set(headerMode.get()) 81 } ··· 86 const onEndDrag = useCallback( 87 (e: NativeScrollEvent) => { 88 'worklet' 89 + if (IS_NATIVE) { 90 if (e.velocity && e.velocity.y !== 0) { 91 // If we detect a velocity, wait for onMomentumEnd to snap. 92 return ··· 100 const onMomentumEnd = useCallback( 101 (e: NativeScrollEvent) => { 102 'worklet' 103 + if (IS_NATIVE) { 104 snapToClosestState(e) 105 } 106 }, ··· 111 (e: NativeScrollEvent) => { 112 'worklet' 113 const offsetY = Math.max(0, e.contentOffset.y) 114 + if (IS_NATIVE) { 115 const startDragOffsetValue = startDragOffset.get() 116 const startModeValue = startMode.get() 117 if (startDragOffsetValue === null || startModeValue === null) { ··· 177 178 const emitter = new EventEmitter() 179 180 + if (IS_WEB) { 181 const originalScroll = window.scroll 182 window.scroll = function () { 183 emitter.emit('forced-scroll')
+4 -4
src/view/com/util/PostMeta.tsx
··· 12 import {sanitizeDisplayName} from '#/lib/strings/display-names' 13 import {sanitizeHandle} from '#/lib/strings/handles' 14 import {niceDate} from '#/lib/strings/time' 15 - import {isAndroid} from '#/platform/detection' 16 import {useProfileShadow} from '#/state/cache/profile-shadow' 17 import {precacheProfile} from '#/state/queries/profile' 18 import {atoms as a, platform, useTheme, web} from '#/alf' ··· 21 import {Text} from '#/components/Typography' 22 import {useSimpleVerificationState} from '#/components/verification' 23 import {VerificationCheck} from '#/components/verification/VerificationCheck' 24 import {TimeElapsed} from './TimeElapsed' 25 import {PreviewableUserAvatar} from './UserAvatar' 26 ··· 60 return ( 61 <View 62 style={[ 63 - isAndroid ? a.flex_1 : a.flex_shrink, 64 a.flex_row, 65 a.align_center, 66 a.pb_xs, ··· 152 a.pl_xs, 153 a.text_md, 154 a.leading_tight, 155 - isAndroid && a.flex_grow, 156 a.text_right, 157 t.atoms.text_contrast_medium, 158 web({ 159 whiteSpace: 'nowrap', 160 }), 161 ]}> 162 - {!isAndroid && ( 163 <Text 164 style={[ 165 a.text_md,
··· 12 import {sanitizeDisplayName} from '#/lib/strings/display-names' 13 import {sanitizeHandle} from '#/lib/strings/handles' 14 import {niceDate} from '#/lib/strings/time' 15 import {useProfileShadow} from '#/state/cache/profile-shadow' 16 import {precacheProfile} from '#/state/queries/profile' 17 import {atoms as a, platform, useTheme, web} from '#/alf' ··· 20 import {Text} from '#/components/Typography' 21 import {useSimpleVerificationState} from '#/components/verification' 22 import {VerificationCheck} from '#/components/verification/VerificationCheck' 23 + import {IS_ANDROID} from '#/env' 24 import {TimeElapsed} from './TimeElapsed' 25 import {PreviewableUserAvatar} from './UserAvatar' 26 ··· 60 return ( 61 <View 62 style={[ 63 + IS_ANDROID ? a.flex_1 : a.flex_shrink, 64 a.flex_row, 65 a.align_center, 66 a.pb_xs, ··· 152 a.pl_xs, 153 a.text_md, 154 a.leading_tight, 155 + IS_ANDROID && a.flex_grow, 156 a.text_right, 157 t.atoms.text_contrast_medium, 158 web({ 159 whiteSpace: 'nowrap', 160 }), 161 ]}> 162 + {!IS_ANDROID && ( 163 <Text 164 style={[ 165 a.text_md,
+7 -8
src/view/com/util/UserAvatar.tsx
··· 16 import {useQueryClient} from '@tanstack/react-query' 17 18 import {useActorStatus} from '#/lib/actor-status' 19 - import {isTouchDevice} from '#/lib/browser' 20 import {useHaptics} from '#/lib/haptics' 21 import { 22 useCameraPermission, ··· 30 import {isCancelledError} from '#/lib/strings/errors' 31 import {sanitizeHandle} from '#/lib/strings/handles' 32 import {logger} from '#/logger' 33 - import {isAndroid, isNative, isWeb} from '#/platform/detection' 34 import { 35 type ComposerImage, 36 compressImage, ··· 59 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 60 import * as Menu from '#/components/Menu' 61 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 62 import type * as bsky from '#/types/bsky' 63 64 export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' ··· 93 onBeforePress?: () => void 94 } 95 96 - const BLUR_AMOUNT = isWeb ? 5 : 100 97 98 let DefaultAvatar = ({ 99 type, ··· 311 }, [size, style]) 312 313 return avatar && 314 - !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( 315 <View style={containerStyle}> 316 {usePlainRNImage ? ( 317 <RNImage ··· 428 } 429 430 try { 431 - if (isNative) { 432 onSelectNewAvatar( 433 await compressIfNeeded( 434 await openCropper({ ··· 500 </Menu.Trigger> 501 <Menu.Outer showCancel> 502 <Menu.Group> 503 - {isNative && ( 504 <Menu.Item 505 testID="changeAvatarCameraBtn" 506 label={_(msg`Upload from Camera`)} ··· 517 label={_(msg`Upload from Library`)} 518 onPress={onOpenLibrary}> 519 <Menu.ItemText> 520 - {isNative ? ( 521 <Trans>Upload from Library</Trans> 522 ) : ( 523 <Trans>Upload from Files</Trans> ··· 607 <ProfileHoverCard did={profile.did} disable={disableHoverCard}> 608 {disableNavigation ? ( 609 avatarEl 610 - ) : status.isActive && (isNative || isTouchDevice) ? ( 611 <> 612 <Button 613 label={_(
··· 16 import {useQueryClient} from '@tanstack/react-query' 17 18 import {useActorStatus} from '#/lib/actor-status' 19 import {useHaptics} from '#/lib/haptics' 20 import { 21 useCameraPermission, ··· 29 import {isCancelledError} from '#/lib/strings/errors' 30 import {sanitizeHandle} from '#/lib/strings/handles' 31 import {logger} from '#/logger' 32 import { 33 type ComposerImage, 34 compressImage, ··· 57 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 58 import * as Menu from '#/components/Menu' 59 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 60 + import {IS_ANDROID, IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' 61 import type * as bsky from '#/types/bsky' 62 63 export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' ··· 92 onBeforePress?: () => void 93 } 94 95 + const BLUR_AMOUNT = IS_WEB ? 5 : 100 96 97 let DefaultAvatar = ({ 98 type, ··· 310 }, [size, style]) 311 312 return avatar && 313 + !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? ( 314 <View style={containerStyle}> 315 {usePlainRNImage ? ( 316 <RNImage ··· 427 } 428 429 try { 430 + if (IS_NATIVE) { 431 onSelectNewAvatar( 432 await compressIfNeeded( 433 await openCropper({ ··· 499 </Menu.Trigger> 500 <Menu.Outer showCancel> 501 <Menu.Group> 502 + {IS_NATIVE && ( 503 <Menu.Item 504 testID="changeAvatarCameraBtn" 505 label={_(msg`Upload from Camera`)} ··· 516 label={_(msg`Upload from Library`)} 517 onPress={onOpenLibrary}> 518 <Menu.ItemText> 519 + {IS_NATIVE ? ( 520 <Trans>Upload from Library</Trans> 521 ) : ( 522 <Trans>Upload from Files</Trans> ··· 606 <ProfileHoverCard did={profile.did} disable={disableHoverCard}> 607 {disableNavigation ? ( 608 avatarEl 609 + ) : status.isActive && (IS_NATIVE || IS_WEB_TOUCH_DEVICE) ? ( 610 <> 611 <Button 612 label={_(
+5 -5
src/view/com/util/UserBanner.tsx
··· 14 import {type PickerImage} from '#/lib/media/picker.shared' 15 import {isCancelledError} from '#/lib/strings/errors' 16 import {logger} from '#/logger' 17 - import {isAndroid, isNative} from '#/platform/detection' 18 import { 19 type ComposerImage, 20 compressImage, ··· 36 import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive' 37 import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 38 import * as Menu from '#/components/Menu' 39 40 export function UserBanner({ 41 type, ··· 80 } 81 82 try { 83 - if (isNative) { 84 onSelectNewBanner?.( 85 await compressIfNeeded( 86 await openCropper({ ··· 163 </Menu.Trigger> 164 <Menu.Outer showCancel> 165 <Menu.Group> 166 - {isNative && ( 167 <Menu.Item 168 testID="changeBannerCameraBtn" 169 label={_(msg`Upload from Camera`)} ··· 180 label={_(msg`Upload from Library`)} 181 onPress={onOpenLibrary}> 182 <Menu.ItemText> 183 - {isNative ? ( 184 <Trans>Upload from Library</Trans> 185 ) : ( 186 <Trans>Upload from Files</Trans> ··· 217 /> 218 </> 219 ) : banner && 220 - !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( 221 <Image 222 testID="userBannerImage" 223 style={[styles.bannerImage, t.atoms.bg_contrast_25]}
··· 14 import {type PickerImage} from '#/lib/media/picker.shared' 15 import {isCancelledError} from '#/lib/strings/errors' 16 import {logger} from '#/logger' 17 import { 18 type ComposerImage, 19 compressImage, ··· 35 import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive' 36 import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 37 import * as Menu from '#/components/Menu' 38 + import {IS_ANDROID, IS_NATIVE} from '#/env' 39 40 export function UserBanner({ 41 type, ··· 80 } 81 82 try { 83 + if (IS_NATIVE) { 84 onSelectNewBanner?.( 85 await compressIfNeeded( 86 await openCropper({ ··· 163 </Menu.Trigger> 164 <Menu.Outer showCancel> 165 <Menu.Group> 166 + {IS_NATIVE && ( 167 <Menu.Item 168 testID="changeBannerCameraBtn" 169 label={_(msg`Upload from Camera`)} ··· 180 label={_(msg`Upload from Library`)} 181 onPress={onOpenLibrary}> 182 <Menu.ItemText> 183 + {IS_NATIVE ? ( 184 <Trans>Upload from Library</Trans> 185 ) : ( 186 <Trans>Upload from Files</Trans> ··· 217 /> 218 </> 219 ) : banner && 220 + !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? ( 221 <Image 222 testID="userBannerImage" 223 style={[styles.bannerImage, t.atoms.bg_contrast_25]}
+2 -2
src/view/com/util/ViewSelector.tsx
··· 13 import {usePalette} from '#/lib/hooks/usePalette' 14 import {clamp} from '#/lib/numbers' 15 import {colors, s} from '#/lib/styles' 16 - import {isAndroid} from '#/platform/detection' 17 import {Text} from './text/Text' 18 import {FlatList_INTERNAL} from './Views' 19 ··· 120 renderItem={renderItemInternal} 121 ListFooterComponent={ListFooterComponent} 122 // NOTE sticky header disabled on android due to major performance issues -prf 123 - stickyHeaderIndices={isAndroid ? undefined : STICKY_HEADER_INDICES} 124 onScroll={onScroll} 125 onEndReached={onEndReached} 126 refreshControl={
··· 13 import {usePalette} from '#/lib/hooks/usePalette' 14 import {clamp} from '#/lib/numbers' 15 import {colors, s} from '#/lib/styles' 16 + import {IS_ANDROID} from '#/env' 17 import {Text} from './text/Text' 18 import {FlatList_INTERNAL} from './Views' 19 ··· 120 renderItem={renderItemInternal} 121 ListFooterComponent={ListFooterComponent} 122 // NOTE sticky header disabled on android due to major performance issues -prf 123 + stickyHeaderIndices={IS_ANDROID ? undefined : STICKY_HEADER_INDICES} 124 onScroll={onScroll} 125 onEndReached={onEndReached} 126 refreshControl={
+2 -2
src/view/com/util/fab/FABInner.tsx
··· 12 import {useHaptics} from '#/lib/haptics' 13 import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform' 14 import {clamp} from '#/lib/numbers' 15 - import {isWeb} from '#/platform/detection' 16 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 17 import {ios, useBreakpoints, useTheme} from '#/alf' 18 import {atoms as a} from '#/alf' 19 20 export interface FABProps extends ComponentProps<typeof Pressable> { 21 testID?: string ··· 97 }, 98 outer: { 99 // @ts-ignore web-only 100 - position: isWeb ? 'fixed' : 'absolute', 101 zIndex: 1, 102 cursor: 'pointer', 103 },
··· 12 import {useHaptics} from '#/lib/haptics' 13 import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform' 14 import {clamp} from '#/lib/numbers' 15 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 16 import {ios, useBreakpoints, useTheme} from '#/alf' 17 import {atoms as a} from '#/alf' 18 + import {IS_WEB} from '#/env' 19 20 export interface FABProps extends ComponentProps<typeof Pressable> { 21 testID?: string ··· 97 }, 98 outer: { 99 // @ts-ignore web-only 100 + position: IS_WEB ? 'fixed' : 'absolute', 101 zIndex: 1, 102 cursor: 'pointer', 103 },
+2 -2
src/view/com/util/forms/Button.tsx
··· 153 async (event: GestureResponderEvent) => { 154 event.stopPropagation() 155 event.preventDefault() 156 - withLoading && setIsLoading(true) 157 await onPress?.(event) 158 - withLoading && setIsLoading(false) 159 }, 160 [onPress, withLoading], 161 )
··· 153 async (event: GestureResponderEvent) => { 154 event.stopPropagation() 155 event.preventDefault() 156 + if (withLoading) setIsLoading(true) 157 await onPress?.(event) 158 + if (withLoading) setIsLoading(false) 159 }, 160 [onPress, withLoading], 161 )
+2 -2
src/view/com/util/layouts/LoggedOutLayout.tsx
··· 5 import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' 6 import {usePalette} from '#/lib/hooks/usePalette' 7 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 8 - import {isWeb} from '#/platform/detection' 9 import {atoms as a} from '#/alf' 10 import {Text} from '../text/Text' 11 12 export const LoggedOutLayout = ({ ··· 79 contentContainerStyle={styles.scrollViewContentContainer} 80 keyboardShouldPersistTaps="handled" 81 keyboardDismissMode="on-drag"> 82 - <View style={[styles.contentWrapper, isWeb && a.my_auto]}> 83 {children} 84 </View> 85 </ScrollView>
··· 5 import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' 6 import {usePalette} from '#/lib/hooks/usePalette' 7 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 8 import {atoms as a} from '#/alf' 9 + import {IS_WEB} from '#/env' 10 import {Text} from '../text/Text' 11 12 export const LoggedOutLayout = ({ ··· 79 contentContainerStyle={styles.scrollViewContentContainer} 80 keyboardShouldPersistTaps="handled" 81 keyboardDismissMode="on-drag"> 82 + <View style={[styles.contentWrapper, IS_WEB && a.my_auto]}> 83 {children} 84 </View> 85 </ScrollView>
+4 -3
src/view/com/util/text/Text.tsx
··· 5 import {lh, s} from '#/lib/styles' 6 import {type TypographyVariant, useTheme} from '#/lib/ThemeContext' 7 import {logger} from '#/logger' 8 - import {isIOS, isWeb} from '#/platform/detection' 9 import {applyFonts, useAlf} from '#/alf' 10 import { 11 childHasEmoji, 12 renderChildrenWithEmoji, 13 type StringChild, 14 } from '#/alf/typography' 15 16 export type CustomTextProps = Omit<TextProps, 'children'> & { 17 type?: TypographyVariant ··· 51 if (__DEV__) { 52 if (!emoji && childHasEmoji(children)) { 53 logger.warn( 54 `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`, 55 ) 56 } ··· 80 } 81 82 return { 83 - uiTextView: selectable && isIOS, 84 selectable, 85 style: flattened, 86 - dataSet: isWeb 87 ? Object.assign({tooltip: title}, dataSet || {}) 88 : undefined, 89 ...props,
··· 5 import {lh, s} from '#/lib/styles' 6 import {type TypographyVariant, useTheme} from '#/lib/ThemeContext' 7 import {logger} from '#/logger' 8 import {applyFonts, useAlf} from '#/alf' 9 import { 10 childHasEmoji, 11 renderChildrenWithEmoji, 12 type StringChild, 13 } from '#/alf/typography' 14 + import {IS_IOS, IS_WEB} from '#/env' 15 16 export type CustomTextProps = Omit<TextProps, 'children'> & { 17 type?: TypographyVariant ··· 51 if (__DEV__) { 52 if (!emoji && childHasEmoji(children)) { 53 logger.warn( 54 + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string 55 `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`, 56 ) 57 } ··· 81 } 82 83 return { 84 + uiTextView: selectable && IS_IOS, 85 selectable, 86 style: flattened, 87 + dataSet: IS_WEB 88 ? Object.assign({tooltip: title}, dataSet || {}) 89 : undefined, 90 ...props,
+4 -4
src/view/screens/Feeds.tsx
··· 16 } from '#/lib/routes/types' 17 import {cleanError} from '#/lib/strings/errors' 18 import {s} from '#/lib/styles' 19 - import {isNative, isWeb} from '#/platform/detection' 20 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 21 import { 22 type SavedFeedItem, ··· 47 import * as Layout from '#/components/Layout' 48 import {Link} from '#/components/Link' 49 import * as ListCard from '#/components/ListCard' 50 51 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'> 52 ··· 388 const onChangeSearchFocus = React.useCallback( 389 (focus: boolean) => { 390 if (focus && searchBarIndex > -1) { 391 - if (isNative) { 392 // scrollToIndex scrolls the exact right amount, so use if available 393 listRef.current?.scrollToIndex({ 394 index: searchBarIndex, ··· 684 return ( 685 <View 686 style={ 687 - isWeb 688 ? [ 689 a.flex_row, 690 a.px_md, ··· 720 return ( 721 <View 722 style={ 723 - isWeb 724 ? [a.flex_row, a.px_md, a.pt_lg, a.pb_lg, a.gap_md] 725 : [{flexDirection: 'row-reverse'}, a.p_lg, a.gap_md] 726 }>
··· 16 } from '#/lib/routes/types' 17 import {cleanError} from '#/lib/strings/errors' 18 import {s} from '#/lib/styles' 19 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 20 import { 21 type SavedFeedItem, ··· 46 import * as Layout from '#/components/Layout' 47 import {Link} from '#/components/Link' 48 import * as ListCard from '#/components/ListCard' 49 + import {IS_NATIVE, IS_WEB} from '#/env' 50 51 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'> 52 ··· 388 const onChangeSearchFocus = React.useCallback( 389 (focus: boolean) => { 390 if (focus && searchBarIndex > -1) { 391 + if (IS_NATIVE) { 392 // scrollToIndex scrolls the exact right amount, so use if available 393 listRef.current?.scrollToIndex({ 394 index: searchBarIndex, ··· 684 return ( 685 <View 686 style={ 687 + IS_WEB 688 ? [ 689 a.flex_row, 690 a.px_md, ··· 720 return ( 721 <View 722 style={ 723 + IS_WEB 724 ? [a.flex_row, a.px_md, a.pt_lg, a.pb_lg, a.gap_md] 725 : [{flexDirection: 'row-reverse'}, a.p_lg, a.gap_md] 726 }>
+2 -2
src/view/screens/Home.tsx
··· 12 type NativeStackScreenProps, 13 } from '#/lib/routes/types' 14 import {logEvent} from '#/lib/statsig/statsig' 15 - import {isWeb} from '#/platform/detection' 16 import {emitSoftReset} from '#/state/events' 17 import { 18 type SavedFeedSourceInfo, ··· 37 import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed' 38 import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' 39 import * as Layout from '#/components/Layout' 40 import {useDemoMode} from '#/storage/hooks/demo-mode' 41 42 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'> ··· 48 usePinnedFeedsInfos() 49 50 React.useEffect(() => { 51 - if (isWeb && !currentAccount) { 52 const getParams = new URLSearchParams(window.location.search) 53 const splash = getParams.get('splash') 54 if (splash === 'true') {
··· 12 type NativeStackScreenProps, 13 } from '#/lib/routes/types' 14 import {logEvent} from '#/lib/statsig/statsig' 15 import {emitSoftReset} from '#/state/events' 16 import { 17 type SavedFeedSourceInfo, ··· 36 import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed' 37 import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' 38 import * as Layout from '#/components/Layout' 39 + import {IS_WEB} from '#/env' 40 import {useDemoMode} from '#/storage/hooks/demo-mode' 41 42 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'> ··· 48 usePinnedFeedsInfos() 49 50 React.useEffect(() => { 51 + if (IS_WEB && !currentAccount) { 52 const getParams = new URLSearchParams(window.location.search) 53 const splash = getParams.get('splash') 54 if (splash === 'true') {
+3 -3
src/view/screens/Notifications.tsx
··· 14 } from '#/lib/routes/types' 15 import {s} from '#/lib/styles' 16 import {logger} from '#/logger' 17 - import {isNative} from '#/platform/detection' 18 import {emitSoftReset, listenSoftReset} from '#/state/events' 19 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 20 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' ··· 40 import * as Layout from '#/components/Layout' 41 import {InlineLinkText, Link} from '#/components/Link' 42 import {Loader} from '#/components/Loader' 43 44 // We don't currently persist this across reloads since 45 // you gotta visit All to clear the badge anyway. ··· 200 // event handlers 201 // = 202 const scrollToTop = useCallback(() => { 203 - scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0}) 204 setMinimalShellMode(false) 205 }, [scrollElRef, setMinimalShellMode]) 206 ··· 230 // on focus, check for latest, but only invalidate if the user 231 // isnt scrolled down to avoid moving content underneath them 232 let currentIsScrolledDown 233 - if (isNative) { 234 currentIsScrolledDown = isScrolledDown 235 } else { 236 // On the web, this isn't always updated in time so
··· 14 } from '#/lib/routes/types' 15 import {s} from '#/lib/styles' 16 import {logger} from '#/logger' 17 import {emitSoftReset, listenSoftReset} from '#/state/events' 18 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 19 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' ··· 39 import * as Layout from '#/components/Layout' 40 import {InlineLinkText, Link} from '#/components/Link' 41 import {Loader} from '#/components/Loader' 42 + import {IS_NATIVE} from '#/env' 43 44 // We don't currently persist this across reloads since 45 // you gotta visit All to clear the badge anyway. ··· 200 // event handlers 201 // = 202 const scrollToTop = useCallback(() => { 203 + scrollElRef.current?.scrollToOffset({animated: IS_NATIVE, offset: 0}) 204 setMinimalShellMode(false) 205 }, [scrollElRef, setMinimalShellMode]) 206 ··· 230 // on focus, check for latest, but only invalidate if the user 231 // isnt scrolled down to avoid moving content underneath them 232 let currentIsScrolledDown 233 + if (IS_NATIVE) { 234 currentIsScrolledDown = isScrolledDown 235 } else { 236 // On the web, this isn't always updated in time so
+2 -2
src/view/shell/Drawer.tsx
··· 13 import {type NavigationProp} from '#/lib/routes/types' 14 import {sanitizeHandle} from '#/lib/strings/handles' 15 import {colors} from '#/lib/styles' 16 - import {isWeb} from '#/platform/detection' 17 import {emitSoftReset} from '#/state/events' 18 import {useDisableFollowersMetrics} from '#/state/preferences/disable-followers-metrics' 19 import {useDisableFollowingMetrics} from '#/state/preferences/disable-following-metrics' ··· 58 import {Text} from '#/components/Typography' 59 import {useSimpleVerificationState} from '#/components/verification' 60 import {VerificationCheck} from '#/components/verification/VerificationCheck' 61 62 const iconWidth = 26 63 ··· 183 (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => { 184 const state = navigation.getState() 185 setDrawerOpen(false) 186 - if (isWeb) { 187 // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh 188 if (tab === 'MyProfile') { 189 navigation.navigate('Profile', {name: currentAccount!.handle})
··· 13 import {type NavigationProp} from '#/lib/routes/types' 14 import {sanitizeHandle} from '#/lib/strings/handles' 15 import {colors} from '#/lib/styles' 16 import {emitSoftReset} from '#/state/events' 17 import {useDisableFollowersMetrics} from '#/state/preferences/disable-followers-metrics' 18 import {useDisableFollowingMetrics} from '#/state/preferences/disable-following-metrics' ··· 57 import {Text} from '#/components/Typography' 58 import {useSimpleVerificationState} from '#/components/verification' 59 import {VerificationCheck} from '#/components/verification/VerificationCheck' 60 + import {IS_WEB} from '#/env' 61 62 const iconWidth = 26 63 ··· 183 (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => { 184 const state = navigation.getState() 185 setDrawerOpen(false) 186 + if (IS_WEB) { 187 // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh 188 if (tab === 'MyProfile') { 189 navigation.navigate('Profile', {name: currentAccount!.handle})
+3 -3
src/view/shell/createNativeStackNavigatorWithAuth.tsx
··· 27 28 import {PWI_ENABLED} from '#/lib/build-flags' 29 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 30 - import {isNative, isWeb} from '#/platform/detection' 31 import {useSession} from '#/state/session' 32 import {useOnboardingState} from '#/state/shell' 33 import { ··· 39 import {SignupQueued} from '#/screens/SignupQueued' 40 import {atoms as a, useLayoutBreakpoints} from '#/alf' 41 import {PolicyUpdateOverlay} from '#/components/PolicyUpdateOverlay' 42 import {BottomBarWeb} from './bottom-bar/BottomBarWeb' 43 import {DesktopLeftNav} from './desktop/LeftNav' 44 import {DesktopRightNav} from './desktop/RightNav' ··· 115 const {setShowLoggedOut} = useLoggedOutViewControls() 116 const {isMobile} = useWebMediaQueries() 117 const {leftNavMinimal} = useLayoutBreakpoints() 118 - if (!hasSession && (!PWI_ENABLED || activeRouteRequiresAuth || isNative)) { 119 return <LoggedOut /> 120 } 121 if (hasSession && currentAccount?.signupQueued) { ··· 158 describe={describe} 159 /> 160 </View> 161 - {isWeb && ( 162 <> 163 {showBottomBar ? <BottomBarWeb /> : <DesktopLeftNav />} 164 {!isMobile && <DesktopRightNav routeName={activeRoute.name} />}
··· 27 28 import {PWI_ENABLED} from '#/lib/build-flags' 29 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 30 import {useSession} from '#/state/session' 31 import {useOnboardingState} from '#/state/shell' 32 import { ··· 38 import {SignupQueued} from '#/screens/SignupQueued' 39 import {atoms as a, useLayoutBreakpoints} from '#/alf' 40 import {PolicyUpdateOverlay} from '#/components/PolicyUpdateOverlay' 41 + import {IS_NATIVE, IS_WEB} from '#/env' 42 import {BottomBarWeb} from './bottom-bar/BottomBarWeb' 43 import {DesktopLeftNav} from './desktop/LeftNav' 44 import {DesktopRightNav} from './desktop/RightNav' ··· 115 const {setShowLoggedOut} = useLoggedOutViewControls() 116 const {isMobile} = useWebMediaQueries() 117 const {leftNavMinimal} = useLayoutBreakpoints() 118 + if (!hasSession && (!PWI_ENABLED || activeRouteRequiresAuth || IS_NATIVE)) { 119 return <LoggedOut /> 120 } 121 if (hasSession && currentAccount?.signupQueued) { ··· 158 describe={describe} 159 /> 160 </View> 161 + {IS_WEB && ( 162 <> 163 {showBottomBar ? <BottomBarWeb /> : <DesktopLeftNav />} 164 {!isMobile && <DesktopRightNav routeName={activeRoute.name} />}
+8 -6
src/view/shell/index.tsx
··· 11 import {useNotificationsHandler} from '#/lib/hooks/useNotificationHandler' 12 import {useNotificationsRegistration} from '#/lib/notifications/notifications' 13 import {isStateAtTabRoot} from '#/lib/routes/helpers' 14 - import {isAndroid, isIOS} from '#/platform/detection' 15 import {useDialogFullyExpandedCountContext} from '#/state/dialogs' 16 import {useSession} from '#/state/session' 17 import { ··· 43 import {useAgeAssurance} from '#/ageAssurance' 44 import {NoAccessScreen} from '#/ageAssurance/components/NoAccessScreen' 45 import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay' 46 import {RoutesContainer, TabsNavigator} from '#/Navigation' 47 import {BottomSheetOutlet} from '../../../modules/bottom-sheet' 48 import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' ··· 60 useNotificationsHandler() 61 62 useEffect(() => { 63 - if (isAndroid) { 64 const listener = BackHandler.addEventListener('hardwareBackPress', () => { 65 return closeAnyActiveElement() 66 }) ··· 80 const navigation = useNavigation() 81 const dedupe = useDedupe(1000) 82 useEffect(() => { 83 - if (!isAndroid) return 84 const onFocusOrBlur = () => { 85 setTimeout(() => { 86 dedupe(updateActiveViewAsync) ··· 190 swipeEdgeWidth={winDim.width} 191 swipeMinVelocity={100} 192 swipeMinDistance={10} 193 - drawerType={isIOS ? 'slide' : 'front'} 194 overlayStyle={{ 195 backgroundColor: select(t.name, { 196 light: 'rgba(0, 57, 117, 0.1)', 197 - dark: isAndroid ? 'rgba(16, 133, 254, 0.1)' : 'rgba(1, 82, 168, 0.1)', 198 dim: 'rgba(10, 13, 16, 0.8)', 199 }), 200 }}> ··· 220 <SystemBars 221 style={{ 222 statusBar: 223 - t.name !== 'light' || (isIOS && fullyExpandedCount > 0) 224 ? 'light' 225 : 'dark', 226 navigationBar: t.name !== 'light' ? 'light' : 'dark',
··· 11 import {useNotificationsHandler} from '#/lib/hooks/useNotificationHandler' 12 import {useNotificationsRegistration} from '#/lib/notifications/notifications' 13 import {isStateAtTabRoot} from '#/lib/routes/helpers' 14 import {useDialogFullyExpandedCountContext} from '#/state/dialogs' 15 import {useSession} from '#/state/session' 16 import { ··· 42 import {useAgeAssurance} from '#/ageAssurance' 43 import {NoAccessScreen} from '#/ageAssurance/components/NoAccessScreen' 44 import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay' 45 + import {IS_ANDROID, IS_IOS} from '#/env' 46 import {RoutesContainer, TabsNavigator} from '#/Navigation' 47 import {BottomSheetOutlet} from '../../../modules/bottom-sheet' 48 import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' ··· 60 useNotificationsHandler() 61 62 useEffect(() => { 63 + if (IS_ANDROID) { 64 const listener = BackHandler.addEventListener('hardwareBackPress', () => { 65 return closeAnyActiveElement() 66 }) ··· 80 const navigation = useNavigation() 81 const dedupe = useDedupe(1000) 82 useEffect(() => { 83 + if (!IS_ANDROID) return 84 const onFocusOrBlur = () => { 85 setTimeout(() => { 86 dedupe(updateActiveViewAsync) ··· 190 swipeEdgeWidth={winDim.width} 191 swipeMinVelocity={100} 192 swipeMinDistance={10} 193 + drawerType={IS_IOS ? 'slide' : 'front'} 194 overlayStyle={{ 195 backgroundColor: select(t.name, { 196 light: 'rgba(0, 57, 117, 0.1)', 197 + dark: IS_ANDROID 198 + ? 'rgba(16, 133, 254, 0.1)' 199 + : 'rgba(1, 82, 168, 0.1)', 200 dim: 'rgba(10, 13, 16, 0.8)', 201 }), 202 }}> ··· 222 <SystemBars 223 style={{ 224 statusBar: 225 + t.name !== 'light' || (IS_IOS && fullyExpandedCount > 0) 226 ? 'light' 227 : 'dark', 228 navigationBar: t.name !== 'light' ? 'light' : 'dark',
+826 -250
yarn.lock
··· 3788 dependencies: 3789 "@types/hammerjs" "^2.0.36" 3790 3791 "@emnapi/runtime@^1.2.0": 3792 version "1.3.1" 3793 resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" 3794 integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw== 3795 dependencies: 3796 tslib "^2.4.0" 3797 ··· 3924 dependencies: 3925 eslint-visitor-keys "^3.4.3" 3926 3927 "@eslint-community/regexpp@^4.10.0": 3928 version "4.12.1" 3929 resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" 3930 integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== 3931 3932 - "@eslint-community/regexpp@^4.6.1": 3933 - version "4.6.2" 3934 - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" 3935 - integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== 3936 3937 - "@eslint/eslintrc@^2.1.2": 3938 - version "2.1.2" 3939 - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" 3940 - integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== 3941 dependencies: 3942 ajv "^6.12.4" 3943 debug "^4.3.2" 3944 - espree "^9.6.0" 3945 - globals "^13.19.0" 3946 ignore "^5.2.0" 3947 import-fresh "^3.2.1" 3948 - js-yaml "^4.1.0" 3949 minimatch "^3.1.2" 3950 strip-json-comments "^3.1.1" 3951 3952 - "@eslint/js@^8.47.0": 3953 - version "8.47.0" 3954 - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d" 3955 - integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og== 3956 3957 "@expo/cli@54.0.18": 3958 version "54.0.18" ··· 4675 resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.4.tgz#42a7f244fd3dd777792bfb74b8c6340ae9182f37" 4676 integrity sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ== 4677 4678 - "@humanwhocodes/config-array@^0.11.10": 4679 - version "0.11.10" 4680 - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" 4681 - integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== 4682 dependencies: 4683 - "@humanwhocodes/object-schema" "^1.2.1" 4684 - debug "^4.1.1" 4685 - minimatch "^3.0.5" 4686 4687 "@humanwhocodes/module-importer@^1.0.1": 4688 version "1.0.1" 4689 resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" 4690 integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== 4691 4692 - "@humanwhocodes/object-schema@^1.2.1": 4693 - version "1.2.1" 4694 - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" 4695 - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== 4696 4697 "@ide/backoff@^1.0.0": 4698 version "1.0.0" ··· 5393 "@expo/image-utils" "^0.8.7" 5394 xcode "^3.0.1" 5395 5396 "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": 5397 version "5.1.1-v1" 5398 resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" ··· 5430 resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 5431 integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 5432 5433 - "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": 5434 version "1.2.8" 5435 resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 5436 integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== ··· 6419 resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" 6420 integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== 6421 6422 - "@rtsao/scc@^1.1.0": 6423 - version "1.1.0" 6424 - resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" 6425 - integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== 6426 - 6427 "@sentry-internal/browser-utils@8.55.0": 6428 version "8.55.0" 6429 resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz#d89bae423edd29c39f01285c8e2d59ce9289d9a6" ··· 7369 resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" 7370 integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== 7371 7372 "@types/babel__core@^7.1.14": 7373 version "7.20.1" 7374 resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" ··· 7477 resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" 7478 integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== 7479 7480 "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": 7481 version "4.17.35" 7482 resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" ··· 7595 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" 7596 integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== 7597 7598 - "@types/json5@^0.0.29": 7599 - version "0.0.29" 7600 - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" 7601 - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== 7602 7603 "@types/lodash.chunk@^4.2.7": 7604 version "4.2.7" ··· 7774 dependencies: 7775 "@types/yargs-parser" "*" 7776 7777 - "@typescript-eslint/eslint-plugin@^7.1.1", "@typescript-eslint/eslint-plugin@^7.18.0": 7778 version "7.18.0" 7779 resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3" 7780 integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== ··· 7789 natural-compare "^1.4.0" 7790 ts-api-utils "^1.3.0" 7791 7792 - "@typescript-eslint/parser@^7.1.1", "@typescript-eslint/parser@^7.18.0": 7793 version "7.18.0" 7794 resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.18.0.tgz#83928d0f1b7f4afa974098c64b5ce6f9051f96a0" 7795 integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== ··· 7800 "@typescript-eslint/visitor-keys" "7.18.0" 7801 debug "^4.3.4" 7802 7803 "@typescript-eslint/scope-manager@5.62.0": 7804 version "5.62.0" 7805 resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" ··· 7816 "@typescript-eslint/types" "7.18.0" 7817 "@typescript-eslint/visitor-keys" "7.18.0" 7818 7819 "@typescript-eslint/type-utils@7.18.0": 7820 version "7.18.0" 7821 resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" ··· 7826 debug "^4.3.4" 7827 ts-api-utils "^1.3.0" 7828 7829 "@typescript-eslint/types@5.62.0": 7830 version "5.62.0" 7831 resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" ··· 7836 resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" 7837 integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== 7838 7839 "@typescript-eslint/typescript-estree@5.62.0": 7840 version "5.62.0" 7841 resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" ··· 7863 semver "^7.6.0" 7864 ts-api-utils "^1.3.0" 7865 7866 "@typescript-eslint/utils@7.18.0": 7867 version "7.18.0" 7868 resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" ··· 7873 "@typescript-eslint/types" "7.18.0" 7874 "@typescript-eslint/typescript-estree" "7.18.0" 7875 7876 - "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.61.0": 7877 version "5.62.0" 7878 resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" 7879 integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== ··· 7903 "@typescript-eslint/types" "7.18.0" 7904 eslint-visitor-keys "^3.4.3" 7905 7906 "@ungap/structured-clone@^1.3.0": 7907 version "1.3.0" 7908 resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" 7909 integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== 7910 7911 "@urql/core@^5.0.0", "@urql/core@^5.0.6": 7912 version "5.0.8" 7913 resolved "https://registry.yarnpkg.com/@urql/core/-/core-5.0.8.tgz#eba39eaa2bf9a0a963383e87a65cba7a9ca794bd" ··· 8136 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" 8137 integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== 8138 8139 - acorn@^8.1.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: 8140 version "8.10.0" 8141 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" 8142 integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== 8143 8144 agent-base@6: 8145 version "6.0.2" ··· 8413 es-shim-unscopables "^1.0.0" 8414 get-intrinsic "^1.2.1" 8415 8416 - array.prototype.findlastindex@^1.2.5: 8417 - version "1.2.6" 8418 - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564" 8419 - integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== 8420 dependencies: 8421 - call-bind "^1.0.8" 8422 - call-bound "^1.0.4" 8423 define-properties "^1.2.1" 8424 - es-abstract "^1.23.9" 8425 es-errors "^1.3.0" 8426 - es-object-atoms "^1.1.1" 8427 - es-shim-unscopables "^1.1.0" 8428 8429 array.prototype.flat@^1.3.1: 8430 version "1.3.1" ··· 8436 es-abstract "^1.20.4" 8437 es-shim-unscopables "^1.0.0" 8438 8439 - array.prototype.flat@^1.3.2: 8440 - version "1.3.3" 8441 - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" 8442 - integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== 8443 - dependencies: 8444 - call-bind "^1.0.8" 8445 - define-properties "^1.2.1" 8446 - es-abstract "^1.23.5" 8447 - es-shim-unscopables "^1.0.2" 8448 - 8449 array.prototype.flatmap@^1.3.1: 8450 version "1.3.1" 8451 resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" ··· 8456 es-abstract "^1.20.4" 8457 es-shim-unscopables "^1.0.0" 8458 8459 - array.prototype.flatmap@^1.3.2: 8460 version "1.3.3" 8461 resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" 8462 integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== ··· 8476 es-abstract "^1.20.4" 8477 es-shim-unscopables "^1.0.0" 8478 get-intrinsic "^1.1.3" 8479 8480 arraybuffer.prototype.slice@^1.0.1: 8481 version "1.0.1" ··· 9000 dependencies: 9001 fill-range "^7.0.1" 9002 9003 brorand@^1.1.0: 9004 version "1.1.0" 9005 resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" ··· 9521 resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" 9522 integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== 9523 9524 commondir@^1.0.1: 9525 version "1.0.1" 9526 resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" ··· 9706 dependencies: 9707 node-fetch "^2.6.12" 9708 9709 - cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: 9710 version "7.0.3" 9711 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 9712 integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== ··· 9715 shebang-command "^2.0.0" 9716 which "^2.0.1" 9717 9718 crypto-random-string@^2.0.0: 9719 version "2.0.0" 9720 resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" ··· 9978 dependencies: 9979 ms "2.1.2" 9980 9981 - debug@^3.1.0, debug@^3.2.7: 9982 version "3.2.7" 9983 resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 9984 integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== ··· 9996 version "4.4.0" 9997 resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" 9998 integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== 9999 dependencies: 10000 ms "^2.1.3" 10001 ··· 10219 dependencies: 10220 esutils "^2.0.2" 10221 10222 - doctrine@^3.0.0: 10223 - version "3.0.0" 10224 - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 10225 - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 10226 - dependencies: 10227 - esutils "^2.0.2" 10228 - 10229 dom-converter@^0.2.0: 10230 version "0.2.0" 10231 resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" ··· 10481 dependencies: 10482 stackframe "^1.3.4" 10483 10484 es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.3: 10485 version "1.22.1" 10486 resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" ··· 10658 iterator.prototype "^1.1.0" 10659 safe-array-concat "^1.0.0" 10660 10661 es-module-lexer@^1.2.1: 10662 version "1.3.0" 10663 resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" ··· 10696 dependencies: 10697 has "^1.0.3" 10698 10699 - es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0: 10700 version "1.1.0" 10701 resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" 10702 integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== ··· 10805 resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" 10806 integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== 10807 10808 - eslint-import-resolver-node@^0.3.9: 10809 - version "0.3.9" 10810 - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" 10811 - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== 10812 dependencies: 10813 - debug "^3.2.7" 10814 - is-core-module "^2.13.0" 10815 - resolve "^1.22.4" 10816 10817 - eslint-module-utils@^2.12.0: 10818 - version "2.12.0" 10819 - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b" 10820 - integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg== 10821 dependencies: 10822 - debug "^3.2.7" 10823 10824 "eslint-plugin-bsky-internal@link:./eslint": 10825 version "0.0.0" ··· 10833 escape-string-regexp "^1.0.5" 10834 ignore "^5.0.5" 10835 10836 - eslint-plugin-ft-flow@^2.0.1, eslint-plugin-ft-flow@^2.0.3: 10837 version "2.0.3" 10838 resolved "https://registry.yarnpkg.com/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz#3b3c113c41902bcbacf0e22b536debcfc3c819e8" 10839 integrity sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg== ··· 10841 lodash "^4.17.21" 10842 string-natural-compare "^3.0.1" 10843 10844 - eslint-plugin-import@^2.31.0: 10845 - version "2.31.0" 10846 - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7" 10847 - integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== 10848 dependencies: 10849 - "@rtsao/scc" "^1.1.0" 10850 - array-includes "^3.1.8" 10851 - array.prototype.findlastindex "^1.2.5" 10852 - array.prototype.flat "^1.3.2" 10853 - array.prototype.flatmap "^1.3.2" 10854 - debug "^3.2.7" 10855 - doctrine "^2.1.0" 10856 - eslint-import-resolver-node "^0.3.9" 10857 - eslint-module-utils "^2.12.0" 10858 - hasown "^2.0.2" 10859 - is-core-module "^2.15.1" 10860 is-glob "^4.0.3" 10861 - minimatch "^3.1.2" 10862 - object.fromentries "^2.0.8" 10863 - object.groupby "^1.0.3" 10864 - object.values "^1.2.0" 10865 - semver "^6.3.1" 10866 - string.prototype.trimend "^1.0.8" 10867 - tsconfig-paths "^3.15.0" 10868 10869 eslint-plugin-jest@^27.9.0: 10870 version "27.9.0" ··· 10873 dependencies: 10874 "@typescript-eslint/utils" "^5.10.0" 10875 10876 - eslint-plugin-lingui@^0.2.0: 10877 - version "0.2.0" 10878 - resolved "https://registry.yarnpkg.com/eslint-plugin-lingui/-/eslint-plugin-lingui-0.2.0.tgz#4fd3355e964544f5d02fce0bea67187bf2f3ac3c" 10879 - integrity sha512-o63ySrDZlsujAaa3ybiFAjhkE1LHaKw5Z5GztiKHu1R+40tVOShy1XKvM+Q+vBykRXV9UrxE1oR79pUOSrsLVA== 10880 dependencies: 10881 - "@typescript-eslint/utils" "^5.61.0" 10882 10883 eslint-plugin-react-compiler@^19.1.0-rc.2: 10884 version "19.1.0-rc.2" ··· 10897 resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" 10898 integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== 10899 10900 - eslint-plugin-react-native-a11y@^3.3.0: 10901 - version "3.3.0" 10902 - resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-a11y/-/eslint-plugin-react-native-a11y-3.3.0.tgz#0485a8f18474bf54ec68d004b50167f75ffbf201" 10903 - integrity sha512-21bIs/0yROcMq7KtAG+OVNDWAh8M+6scII0iXcO3i9NYHe2xZ443yPs5KSUMSvQJeRLLjuKB7V5saqNjoMWDHA== 10904 dependencies: 10905 "@babel/runtime" "^7.15.4" 10906 ast-types-flow "^0.0.7" ··· 10919 "@babel/traverse" "^7.7.4" 10920 eslint-plugin-react-native-globals "^0.1.1" 10921 10922 - eslint-plugin-react@^7.30.1, eslint-plugin-react@^7.33.2: 10923 version "7.33.2" 10924 resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" 10925 integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== ··· 10941 semver "^6.3.1" 10942 string.prototype.matchall "^4.0.8" 10943 10944 - eslint-plugin-simple-import-sort@^12.0.0: 10945 - version "12.0.0" 10946 - resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.0.0.tgz#3cfa05d74509bd4dc329a956938823812194dbb6" 10947 - integrity sha512-8o0dVEdAkYap0Cn5kNeklaKcT1nUsa3LITWEuFk3nJifOoD+5JQGoyDUW2W/iPWwBsNBJpyJS9y4je/BgxLcyQ== 10948 10949 eslint-scope@5.1.1, eslint-scope@^5.1.1: 10950 version "5.1.1" ··· 10954 esrecurse "^4.3.0" 10955 estraverse "^4.1.1" 10956 10957 - eslint-scope@^7.2.2: 10958 - version "7.2.2" 10959 - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" 10960 - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== 10961 dependencies: 10962 esrecurse "^4.3.0" 10963 estraverse "^5.2.0" ··· 10967 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" 10968 integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== 10969 10970 - eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: 10971 version "3.4.3" 10972 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" 10973 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== 10974 10975 - eslint@^8.19.0: 10976 - version "8.47.0" 10977 - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.47.0.tgz#c95f9b935463fb4fad7005e626c7621052e90806" 10978 - integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q== 10979 dependencies: 10980 - "@eslint-community/eslint-utils" "^4.2.0" 10981 - "@eslint-community/regexpp" "^4.6.1" 10982 - "@eslint/eslintrc" "^2.1.2" 10983 - "@eslint/js" "^8.47.0" 10984 - "@humanwhocodes/config-array" "^0.11.10" 10985 "@humanwhocodes/module-importer" "^1.0.1" 10986 - "@nodelib/fs.walk" "^1.2.8" 10987 ajv "^6.12.4" 10988 chalk "^4.0.0" 10989 - cross-spawn "^7.0.2" 10990 debug "^4.3.2" 10991 - doctrine "^3.0.0" 10992 escape-string-regexp "^4.0.0" 10993 - eslint-scope "^7.2.2" 10994 - eslint-visitor-keys "^3.4.3" 10995 - espree "^9.6.1" 10996 - esquery "^1.4.2" 10997 esutils "^2.0.2" 10998 fast-deep-equal "^3.1.3" 10999 - file-entry-cache "^6.0.1" 11000 find-up "^5.0.0" 11001 glob-parent "^6.0.2" 11002 - globals "^13.19.0" 11003 - graphemer "^1.4.0" 11004 ignore "^5.2.0" 11005 imurmurhash "^0.1.4" 11006 is-glob "^4.0.0" 11007 - is-path-inside "^3.0.3" 11008 - js-yaml "^4.1.0" 11009 json-stable-stringify-without-jsonify "^1.0.1" 11010 - levn "^0.4.1" 11011 lodash.merge "^4.6.2" 11012 minimatch "^3.1.2" 11013 natural-compare "^1.4.0" 11014 optionator "^0.9.3" 11015 - strip-ansi "^6.0.1" 11016 - text-table "^0.2.0" 11017 11018 - espree@^9.6.0, espree@^9.6.1: 11019 - version "9.6.1" 11020 - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" 11021 - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== 11022 dependencies: 11023 - acorn "^8.9.0" 11024 acorn-jsx "^5.3.2" 11025 - eslint-visitor-keys "^3.4.1" 11026 11027 esprima@^4.0.0, esprima@^4.0.1: 11028 version "4.0.1" 11029 resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 11030 integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 11031 11032 - esquery@^1.4.2: 11033 - version "1.5.0" 11034 - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" 11035 - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== 11036 dependencies: 11037 estraverse "^5.1.0" 11038 ··· 11718 dependencies: 11719 escape-string-regexp "^1.0.5" 11720 11721 - file-entry-cache@^6.0.1: 11722 - version "6.0.1" 11723 - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" 11724 - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== 11725 dependencies: 11726 - flat-cache "^3.0.4" 11727 11728 file-loader@6.2.0: 11729 version "6.2.0" ··· 11751 version "7.0.1" 11752 resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 11753 integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 11754 dependencies: 11755 to-regex-range "^5.0.1" 11756 ··· 11831 dependencies: 11832 micromatch "^4.0.2" 11833 11834 - flat-cache@^3.0.4: 11835 - version "3.0.4" 11836 - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" 11837 - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== 11838 dependencies: 11839 - flatted "^3.1.0" 11840 - rimraf "^3.0.2" 11841 11842 - flatted@^3.1.0: 11843 - version "3.2.7" 11844 - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" 11845 - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== 11846 11847 flow-enums-runtime@^0.0.6: 11848 version "0.0.6" ··· 12076 es-errors "^1.3.0" 12077 get-intrinsic "^1.2.6" 12078 12079 getenv@^1.0.0: 12080 version "1.0.0" 12081 resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31" ··· 12176 resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 12177 integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 12178 12179 - globals@^13.19.0: 12180 - version "13.21.0" 12181 - resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" 12182 - integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== 12183 - dependencies: 12184 - type-fest "^0.20.2" 12185 12186 globalthis@^1.0.2, globalthis@^1.0.3: 12187 version "1.0.3" ··· 12666 resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" 12667 integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== 12668 12669 image-size@^1.0.2: 12670 version "1.0.2" 12671 resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.2.tgz#d778b6d0ab75b2737c1556dd631652eb963bc486" ··· 12901 call-bound "^1.0.3" 12902 has-tostringtag "^1.0.2" 12903 12904 is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: 12905 version "1.2.7" 12906 resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" ··· 12919 integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== 12920 dependencies: 12921 has "^1.0.3" 12922 - 12923 - is-core-module@^2.15.1, is-core-module@^2.16.0: 12924 - version "2.16.1" 12925 - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" 12926 - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== 12927 - dependencies: 12928 - hasown "^2.0.2" 12929 12930 is-data-view@^1.0.1, is-data-view@^1.0.2: 12931 version "1.0.2" ··· 13037 resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" 13038 integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== 13039 13040 is-number-object@^1.0.4: 13041 version "1.0.7" 13042 resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" ··· 13076 dependencies: 13077 path-is-inside "^1.0.2" 13078 13079 - is-path-inside@^3.0.3: 13080 - version "3.0.3" 13081 - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" 13082 - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== 13083 - 13084 is-plain-obj@^2.1.0: 13085 version "2.1.0" 13086 resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" ··· 13220 dependencies: 13221 call-bind "^1.0.2" 13222 13223 - is-weakref@^1.1.0: 13224 version "1.1.1" 13225 resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" 13226 integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== ··· 13334 has-tostringtag "^1.0.0" 13335 reflect.getprototypeof "^1.0.3" 13336 13337 jackspeak@^2.3.6: 13338 version "2.3.6" 13339 resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" ··· 13934 dependencies: 13935 argparse "^2.0.1" 13936 13937 jsc-safe-url@^0.2.2, jsc-safe-url@^0.2.4: 13938 version "0.2.4" 13939 resolved "https://registry.yarnpkg.com/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz#141c14fbb43791e88d5dc64e85a374575a83477a" ··· 13986 resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" 13987 integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== 13988 13989 json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: 13990 version "2.3.1" 13991 resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" ··· 14024 jsonify "^0.0.1" 14025 object-keys "^1.1.1" 14026 14027 - json5@^1.0.2: 14028 - version "1.0.2" 14029 - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" 14030 - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== 14031 - dependencies: 14032 - minimist "^1.2.0" 14033 - 14034 json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: 14035 version "2.2.3" 14036 resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" ··· 14074 asn1.js "^5.0.1" 14075 bn.js "^4.11.8" 14076 elliptic "^6.4.1" 14077 14078 klaw-sync@^6.0.0: 14079 version "6.0.0" ··· 14791 braces "^3.0.2" 14792 picomatch "^2.3.1" 14793 14794 mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": 14795 version "1.52.0" 14796 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" ··· 14850 resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" 14851 integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== 14852 14853 - minimatch@^10.1.1: 14854 version "10.1.1" 14855 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" 14856 integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== 14857 dependencies: 14858 "@isaacs/brace-expansion" "^5.0.0" 14859 14860 - minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: 14861 version "3.1.2" 14862 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 14863 integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== ··· 14871 dependencies: 14872 brace-expansion "^2.0.1" 14873 14874 - minimatch@^9.0.0: 14875 version "9.0.5" 14876 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" 14877 integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== ··· 15004 resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" 15005 integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== 15006 15007 natural-compare@^1.4.0: 15008 version "1.4.0" 15009 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" ··· 15181 resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" 15182 integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== 15183 15184 - object-inspect@^1.13.3: 15185 version "1.13.4" 15186 resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" 15187 integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== ··· 15230 define-properties "^1.1.4" 15231 es-abstract "^1.20.4" 15232 15233 object.fromentries@^2.0.6: 15234 version "2.0.6" 15235 resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" ··· 15249 es-abstract "^1.23.2" 15250 es-object-atoms "^1.0.0" 15251 15252 - object.groupby@^1.0.3: 15253 - version "1.0.3" 15254 - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" 15255 - integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== 15256 - dependencies: 15257 - call-bind "^1.0.7" 15258 - define-properties "^1.2.1" 15259 - es-abstract "^1.23.2" 15260 - 15261 object.hasown@^1.1.2: 15262 version "1.1.2" 15263 resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" ··· 15275 define-properties "^1.1.4" 15276 es-abstract "^1.20.4" 15277 15278 - object.values@^1.2.0: 15279 version "1.2.1" 15280 resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" 15281 integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== ··· 17216 define-properties "^1.2.0" 17217 set-function-name "^2.0.0" 17218 17219 - regexp.prototype.flags@^1.5.3: 17220 version "1.5.4" 17221 resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" 17222 integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== ··· 17360 dependencies: 17361 global-dirs "^0.1.1" 17362 17363 resolve-workspace-root@^2.0.0: 17364 version "2.0.0" 17365 resolved "https://registry.yarnpkg.com/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz#a0098daa0067cd0efa6eb525c57c8fb4a61e78f8" ··· 17384 path-parse "^1.0.7" 17385 supports-preserve-symlinks-flag "^1.0.0" 17386 17387 - resolve@^1.22.4: 17388 - version "1.22.10" 17389 - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" 17390 - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== 17391 - dependencies: 17392 - is-core-module "^2.16.0" 17393 - path-parse "^1.0.7" 17394 - supports-preserve-symlinks-flag "^1.0.0" 17395 - 17396 resolve@^1.22.8: 17397 version "1.22.8" 17398 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" ··· 17408 integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== 17409 dependencies: 17410 is-core-module "^2.9.0" 17411 path-parse "^1.0.7" 17412 supports-preserve-symlinks-flag "^1.0.0" 17413 ··· 17684 version "7.6.2" 17685 resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" 17686 integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== 17687 17688 send@0.18.0: 17689 version "0.18.0" ··· 18182 resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 18183 integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== 18184 18185 stable@^0.1.8: 18186 version "0.1.8" 18187 resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" ··· 18283 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 18284 integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== 18285 18286 stream-browserify@3.0.0: 18287 version "3.0.0" 18288 resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" ··· 18354 emoji-regex "^9.2.2" 18355 strip-ansi "^7.0.1" 18356 18357 string.prototype.matchall@^4.0.8: 18358 version "4.0.8" 18359 resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" ··· 18368 regexp.prototype.flags "^1.4.3" 18369 side-channel "^1.0.4" 18370 18371 string.prototype.trim@^1.2.10: 18372 version "1.2.10" 18373 resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" ··· 18417 define-properties "^1.2.0" 18418 es-abstract "^1.22.1" 18419 18420 - string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9: 18421 version "1.0.9" 18422 resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" 18423 integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== ··· 18495 integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== 18496 dependencies: 18497 ansi-regex "^6.0.1" 18498 - 18499 - strip-bom@^3.0.0: 18500 - version "3.0.0" 18501 - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 18502 - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== 18503 18504 strip-bom@^4.0.0: 18505 version "4.0.0" ··· 18752 dependencies: 18753 utrie "^1.0.2" 18754 18755 - text-table@^0.2.0: 18756 - version "0.2.0" 18757 - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 18758 - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== 18759 - 18760 thenify-all@^1.0.0: 18761 version "1.6.0" 18762 resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" ··· 18800 resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" 18801 integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== 18802 18803 - tinyglobby@^0.2.11: 18804 version "0.2.15" 18805 resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" 18806 integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== ··· 18918 resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.0.tgz#709c6f2076e511a81557f3d07a0cbd566ae8195c" 18919 integrity sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ== 18920 18921 ts-interface-checker@^0.1.9: 18922 version "0.1.13" 18923 resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" ··· 18947 resolved "https://registry.yarnpkg.com/ts-plugin-sort-import-suggestions/-/ts-plugin-sort-import-suggestions-1.0.4.tgz#d1ed6c235feb8c8bb8b34c625ea75b46e3e62925" 18948 integrity sha512-85n5lm2OQQ+b7aRNK9omU1gmjMNXRsgeLwojm5u4OSY5sVBkAHTcgMQPEeHMNlyyfFW0uXnwgqAU0pNfhD96Bw== 18949 18950 - tsconfig-paths@^3.15.0: 18951 - version "3.15.0" 18952 - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" 18953 - integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== 18954 - dependencies: 18955 - "@types/json5" "^0.0.29" 18956 - json5 "^1.0.2" 18957 - minimist "^1.2.6" 18958 - strip-bom "^3.0.0" 18959 - 18960 tslib@^1.8.1, tslib@^1.9.0: 18961 version "1.14.1" 18962 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" ··· 18997 version "4.0.8" 18998 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 18999 integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 19000 - 19001 - type-fest@^0.20.2: 19002 - version "0.20.2" 19003 - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" 19004 - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 19005 19006 type-fest@^0.21.3: 19007 version "0.21.3" ··· 19126 integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== 19127 optionalDependencies: 19128 rxjs "^7.5.2" 19129 19130 typescript@^5.9.2: 19131 version "5.9.2" ··· 19286 resolved "https://registry.yarnpkg.com/unraw/-/unraw-3.0.0.tgz#73443ed70d2ab09ccbac2b00525602d5991fbbe3" 19287 integrity sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg== 19288 19289 update-browserslist-db@^1.0.11: 19290 version "1.0.11" 19291 resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" ··· 19831 gopd "^1.2.0" 19832 has-tostringtag "^1.0.2" 19833 19834 which-typed-array@^1.1.9: 19835 version "1.1.9" 19836 resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" ··· 20097 resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.3.0.tgz#2cfe81b62d044e0453d1aa3ae7c32a2f36dde9af" 20098 integrity sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw== 20099 20100 zod@3.23.8, zod@^3.14.2, zod@^3.20.2, zod@^3.22.4, zod@^3.23.8: 20101 version "3.23.8" 20102 resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" 20103 integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
··· 3788 dependencies: 3789 "@types/hammerjs" "^2.0.36" 3790 3791 + "@emnapi/core@^1.4.3": 3792 + version "1.8.1" 3793 + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349" 3794 + integrity sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg== 3795 + dependencies: 3796 + "@emnapi/wasi-threads" "1.1.0" 3797 + tslib "^2.4.0" 3798 + 3799 "@emnapi/runtime@^1.2.0": 3800 version "1.3.1" 3801 resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" 3802 integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw== 3803 + dependencies: 3804 + tslib "^2.4.0" 3805 + 3806 + "@emnapi/runtime@^1.4.3": 3807 + version "1.8.1" 3808 + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5" 3809 + integrity sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg== 3810 + dependencies: 3811 + tslib "^2.4.0" 3812 + 3813 + "@emnapi/wasi-threads@1.1.0": 3814 + version "1.1.0" 3815 + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" 3816 + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== 3817 dependencies: 3818 tslib "^2.4.0" 3819 ··· 3946 dependencies: 3947 eslint-visitor-keys "^3.4.3" 3948 3949 + "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": 3950 + version "4.9.1" 3951 + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" 3952 + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== 3953 + dependencies: 3954 + eslint-visitor-keys "^3.4.3" 3955 + 3956 "@eslint-community/regexpp@^4.10.0": 3957 version "4.12.1" 3958 resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" 3959 integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== 3960 3961 + "@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2": 3962 + version "4.12.2" 3963 + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" 3964 + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== 3965 3966 + "@eslint/config-array@^0.21.1": 3967 + version "0.21.1" 3968 + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" 3969 + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== 3970 + dependencies: 3971 + "@eslint/object-schema" "^2.1.7" 3972 + debug "^4.3.1" 3973 + minimatch "^3.1.2" 3974 + 3975 + "@eslint/config-helpers@^0.4.2": 3976 + version "0.4.2" 3977 + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" 3978 + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== 3979 + dependencies: 3980 + "@eslint/core" "^0.17.0" 3981 + 3982 + "@eslint/core@^0.17.0": 3983 + version "0.17.0" 3984 + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" 3985 + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== 3986 + dependencies: 3987 + "@types/json-schema" "^7.0.15" 3988 + 3989 + "@eslint/eslintrc@^3.3.1": 3990 + version "3.3.3" 3991 + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" 3992 + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== 3993 dependencies: 3994 ajv "^6.12.4" 3995 debug "^4.3.2" 3996 + espree "^10.0.1" 3997 + globals "^14.0.0" 3998 ignore "^5.2.0" 3999 import-fresh "^3.2.1" 4000 + js-yaml "^4.1.1" 4001 minimatch "^3.1.2" 4002 strip-json-comments "^3.1.1" 4003 4004 + "@eslint/js@9.39.2", "@eslint/js@^9.39.2": 4005 + version "9.39.2" 4006 + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599" 4007 + integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== 4008 + 4009 + "@eslint/object-schema@^2.1.7": 4010 + version "2.1.7" 4011 + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" 4012 + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== 4013 + 4014 + "@eslint/plugin-kit@^0.4.1": 4015 + version "0.4.1" 4016 + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" 4017 + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== 4018 + dependencies: 4019 + "@eslint/core" "^0.17.0" 4020 + levn "^0.4.1" 4021 4022 "@expo/cli@54.0.18": 4023 version "54.0.18" ··· 4740 resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.4.tgz#42a7f244fd3dd777792bfb74b8c6340ae9182f37" 4741 integrity sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ== 4742 4743 + "@humanfs/core@^0.19.1": 4744 + version "0.19.1" 4745 + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" 4746 + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== 4747 + 4748 + "@humanfs/node@^0.16.6": 4749 + version "0.16.7" 4750 + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" 4751 + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== 4752 dependencies: 4753 + "@humanfs/core" "^0.19.1" 4754 + "@humanwhocodes/retry" "^0.4.0" 4755 4756 "@humanwhocodes/module-importer@^1.0.1": 4757 version "1.0.1" 4758 resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" 4759 integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== 4760 4761 + "@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": 4762 + version "0.4.3" 4763 + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" 4764 + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== 4765 4766 "@ide/backoff@^1.0.0": 4767 version "1.0.0" ··· 5462 "@expo/image-utils" "^0.8.7" 5463 xcode "^3.0.1" 5464 5465 + "@napi-rs/wasm-runtime@^0.2.11": 5466 + version "0.2.12" 5467 + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" 5468 + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== 5469 + dependencies: 5470 + "@emnapi/core" "^1.4.3" 5471 + "@emnapi/runtime" "^1.4.3" 5472 + "@tybys/wasm-util" "^0.10.0" 5473 + 5474 "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": 5475 version "5.1.1-v1" 5476 resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" ··· 5508 resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 5509 integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 5510 5511 + "@nodelib/fs.walk@^1.2.3": 5512 version "1.2.8" 5513 resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 5514 integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== ··· 6497 resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" 6498 integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== 6499 6500 "@sentry-internal/browser-utils@8.55.0": 6501 version "8.55.0" 6502 resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz#d89bae423edd29c39f01285c8e2d59ce9289d9a6" ··· 7442 resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" 7443 integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== 7444 7445 + "@tybys/wasm-util@^0.10.0": 7446 + version "0.10.1" 7447 + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" 7448 + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== 7449 + dependencies: 7450 + tslib "^2.4.0" 7451 + 7452 "@types/babel__core@^7.1.14": 7453 version "7.20.1" 7454 resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" ··· 7557 resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" 7558 integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== 7559 7560 + "@types/estree@^1.0.6": 7561 + version "1.0.8" 7562 + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" 7563 + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== 7564 + 7565 "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": 7566 version "4.17.35" 7567 resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" ··· 7680 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" 7681 integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== 7682 7683 + "@types/json-schema@^7.0.15": 7684 + version "7.0.15" 7685 + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" 7686 + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== 7687 7688 "@types/lodash.chunk@^4.2.7": 7689 version "4.2.7" ··· 7859 dependencies: 7860 "@types/yargs-parser" "*" 7861 7862 + "@typescript-eslint/eslint-plugin@8.53.0": 7863 + version "8.53.0" 7864 + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz#afb966c66a2fdc6158cf81118204a971a36d0fc5" 7865 + integrity sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg== 7866 + dependencies: 7867 + "@eslint-community/regexpp" "^4.12.2" 7868 + "@typescript-eslint/scope-manager" "8.53.0" 7869 + "@typescript-eslint/type-utils" "8.53.0" 7870 + "@typescript-eslint/utils" "8.53.0" 7871 + "@typescript-eslint/visitor-keys" "8.53.0" 7872 + ignore "^7.0.5" 7873 + natural-compare "^1.4.0" 7874 + ts-api-utils "^2.4.0" 7875 + 7876 + "@typescript-eslint/eslint-plugin@^7.1.1": 7877 version "7.18.0" 7878 resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3" 7879 integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== ··· 7888 natural-compare "^1.4.0" 7889 ts-api-utils "^1.3.0" 7890 7891 + "@typescript-eslint/parser@8.53.0": 7892 + version "8.53.0" 7893 + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.53.0.tgz#d8bed6f12dc74e03751e5f947510ff2b165990c6" 7894 + integrity sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg== 7895 + dependencies: 7896 + "@typescript-eslint/scope-manager" "8.53.0" 7897 + "@typescript-eslint/types" "8.53.0" 7898 + "@typescript-eslint/typescript-estree" "8.53.0" 7899 + "@typescript-eslint/visitor-keys" "8.53.0" 7900 + debug "^4.4.3" 7901 + 7902 + "@typescript-eslint/parser@^7.1.1": 7903 version "7.18.0" 7904 resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.18.0.tgz#83928d0f1b7f4afa974098c64b5ce6f9051f96a0" 7905 integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== ··· 7910 "@typescript-eslint/visitor-keys" "7.18.0" 7911 debug "^4.3.4" 7912 7913 + "@typescript-eslint/project-service@8.53.0": 7914 + version "8.53.0" 7915 + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.53.0.tgz#327c67c61c16a1c8b12a440b0779b41eb77cc7df" 7916 + integrity sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg== 7917 + dependencies: 7918 + "@typescript-eslint/tsconfig-utils" "^8.53.0" 7919 + "@typescript-eslint/types" "^8.53.0" 7920 + debug "^4.4.3" 7921 + 7922 "@typescript-eslint/scope-manager@5.62.0": 7923 version "5.62.0" 7924 resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" ··· 7935 "@typescript-eslint/types" "7.18.0" 7936 "@typescript-eslint/visitor-keys" "7.18.0" 7937 7938 + "@typescript-eslint/scope-manager@8.53.0": 7939 + version "8.53.0" 7940 + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz#f922fcbf0d42e72f065297af31779ccf19de9a97" 7941 + integrity sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g== 7942 + dependencies: 7943 + "@typescript-eslint/types" "8.53.0" 7944 + "@typescript-eslint/visitor-keys" "8.53.0" 7945 + 7946 + "@typescript-eslint/tsconfig-utils@8.53.0", "@typescript-eslint/tsconfig-utils@^8.53.0": 7947 + version "8.53.0" 7948 + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz#105279d7969a7abdc8345cc9c57cff83cf910f8f" 7949 + integrity sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA== 7950 + 7951 "@typescript-eslint/type-utils@7.18.0": 7952 version "7.18.0" 7953 resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" ··· 7958 debug "^4.3.4" 7959 ts-api-utils "^1.3.0" 7960 7961 + "@typescript-eslint/type-utils@8.53.0": 7962 + version "8.53.0" 7963 + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz#81a0de5c01fc68f6df0591d03cd8226bda01c91f" 7964 + integrity sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw== 7965 + dependencies: 7966 + "@typescript-eslint/types" "8.53.0" 7967 + "@typescript-eslint/typescript-estree" "8.53.0" 7968 + "@typescript-eslint/utils" "8.53.0" 7969 + debug "^4.4.3" 7970 + ts-api-utils "^2.4.0" 7971 + 7972 "@typescript-eslint/types@5.62.0": 7973 version "5.62.0" 7974 resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" ··· 7979 resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" 7980 integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== 7981 7982 + "@typescript-eslint/types@8.53.0", "@typescript-eslint/types@^8.35.0", "@typescript-eslint/types@^8.53.0": 7983 + version "8.53.0" 7984 + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.53.0.tgz#1adcad3fa32bc2c4cbf3785ba07a5e3151819efb" 7985 + integrity sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ== 7986 + 7987 "@typescript-eslint/typescript-estree@5.62.0": 7988 version "5.62.0" 7989 resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" ··· 8011 semver "^7.6.0" 8012 ts-api-utils "^1.3.0" 8013 8014 + "@typescript-eslint/typescript-estree@8.53.0": 8015 + version "8.53.0" 8016 + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz#7805b46b7a8ce97e91b7bb56fc8b1ba26ca8ef52" 8017 + integrity sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw== 8018 + dependencies: 8019 + "@typescript-eslint/project-service" "8.53.0" 8020 + "@typescript-eslint/tsconfig-utils" "8.53.0" 8021 + "@typescript-eslint/types" "8.53.0" 8022 + "@typescript-eslint/visitor-keys" "8.53.0" 8023 + debug "^4.4.3" 8024 + minimatch "^9.0.5" 8025 + semver "^7.7.3" 8026 + tinyglobby "^0.2.15" 8027 + ts-api-utils "^2.4.0" 8028 + 8029 "@typescript-eslint/utils@7.18.0": 8030 version "7.18.0" 8031 resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" ··· 8036 "@typescript-eslint/types" "7.18.0" 8037 "@typescript-eslint/typescript-estree" "7.18.0" 8038 8039 + "@typescript-eslint/utils@8.53.0", "@typescript-eslint/utils@^8.0.0": 8040 + version "8.53.0" 8041 + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.53.0.tgz#bf0a4e2edaf1afc9abce209fc02f8cab0b74af13" 8042 + integrity sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA== 8043 + dependencies: 8044 + "@eslint-community/eslint-utils" "^4.9.1" 8045 + "@typescript-eslint/scope-manager" "8.53.0" 8046 + "@typescript-eslint/types" "8.53.0" 8047 + "@typescript-eslint/typescript-estree" "8.53.0" 8048 + 8049 + "@typescript-eslint/utils@^5.10.0": 8050 version "5.62.0" 8051 resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" 8052 integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== ··· 8076 "@typescript-eslint/types" "7.18.0" 8077 eslint-visitor-keys "^3.4.3" 8078 8079 + "@typescript-eslint/visitor-keys@8.53.0": 8080 + version "8.53.0" 8081 + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz#9a785664ddae7e3f7e570ad8166e48dbc9c6cf02" 8082 + integrity sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw== 8083 + dependencies: 8084 + "@typescript-eslint/types" "8.53.0" 8085 + eslint-visitor-keys "^4.2.1" 8086 + 8087 "@ungap/structured-clone@^1.3.0": 8088 version "1.3.0" 8089 resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" 8090 integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== 8091 8092 + "@unrs/resolver-binding-android-arm-eabi@1.11.1": 8093 + version "1.11.1" 8094 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" 8095 + integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== 8096 + 8097 + "@unrs/resolver-binding-android-arm64@1.11.1": 8098 + version "1.11.1" 8099 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" 8100 + integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== 8101 + 8102 + "@unrs/resolver-binding-darwin-arm64@1.11.1": 8103 + version "1.11.1" 8104 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" 8105 + integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== 8106 + 8107 + "@unrs/resolver-binding-darwin-x64@1.11.1": 8108 + version "1.11.1" 8109 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" 8110 + integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== 8111 + 8112 + "@unrs/resolver-binding-freebsd-x64@1.11.1": 8113 + version "1.11.1" 8114 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" 8115 + integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== 8116 + 8117 + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": 8118 + version "1.11.1" 8119 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" 8120 + integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== 8121 + 8122 + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": 8123 + version "1.11.1" 8124 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" 8125 + integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== 8126 + 8127 + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": 8128 + version "1.11.1" 8129 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" 8130 + integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== 8131 + 8132 + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": 8133 + version "1.11.1" 8134 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" 8135 + integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== 8136 + 8137 + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": 8138 + version "1.11.1" 8139 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" 8140 + integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== 8141 + 8142 + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": 8143 + version "1.11.1" 8144 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" 8145 + integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== 8146 + 8147 + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": 8148 + version "1.11.1" 8149 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" 8150 + integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== 8151 + 8152 + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": 8153 + version "1.11.1" 8154 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" 8155 + integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== 8156 + 8157 + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": 8158 + version "1.11.1" 8159 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" 8160 + integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== 8161 + 8162 + "@unrs/resolver-binding-linux-x64-musl@1.11.1": 8163 + version "1.11.1" 8164 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" 8165 + integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== 8166 + 8167 + "@unrs/resolver-binding-wasm32-wasi@1.11.1": 8168 + version "1.11.1" 8169 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" 8170 + integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== 8171 + dependencies: 8172 + "@napi-rs/wasm-runtime" "^0.2.11" 8173 + 8174 + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": 8175 + version "1.11.1" 8176 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" 8177 + integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== 8178 + 8179 + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": 8180 + version "1.11.1" 8181 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" 8182 + integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== 8183 + 8184 + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": 8185 + version "1.11.1" 8186 + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" 8187 + integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== 8188 + 8189 "@urql/core@^5.0.0", "@urql/core@^5.0.6": 8190 version "5.0.8" 8191 resolved "https://registry.yarnpkg.com/@urql/core/-/core-5.0.8.tgz#eba39eaa2bf9a0a963383e87a65cba7a9ca794bd" ··· 8414 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" 8415 integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== 8416 8417 + acorn@^8.1.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: 8418 version "8.10.0" 8419 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" 8420 integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== 8421 + 8422 + acorn@^8.15.0: 8423 + version "8.15.0" 8424 + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" 8425 + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== 8426 8427 agent-base@6: 8428 version "6.0.2" ··· 8696 es-shim-unscopables "^1.0.0" 8697 get-intrinsic "^1.2.1" 8698 8699 + array.prototype.findlast@^1.2.5: 8700 + version "1.2.5" 8701 + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" 8702 + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== 8703 dependencies: 8704 + call-bind "^1.0.7" 8705 define-properties "^1.2.1" 8706 + es-abstract "^1.23.2" 8707 es-errors "^1.3.0" 8708 + es-object-atoms "^1.0.0" 8709 + es-shim-unscopables "^1.0.2" 8710 8711 array.prototype.flat@^1.3.1: 8712 version "1.3.1" ··· 8718 es-abstract "^1.20.4" 8719 es-shim-unscopables "^1.0.0" 8720 8721 array.prototype.flatmap@^1.3.1: 8722 version "1.3.1" 8723 resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" ··· 8728 es-abstract "^1.20.4" 8729 es-shim-unscopables "^1.0.0" 8730 8731 + array.prototype.flatmap@^1.3.3: 8732 version "1.3.3" 8733 resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" 8734 integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== ··· 8748 es-abstract "^1.20.4" 8749 es-shim-unscopables "^1.0.0" 8750 get-intrinsic "^1.1.3" 8751 + 8752 + array.prototype.tosorted@^1.1.4: 8753 + version "1.1.4" 8754 + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" 8755 + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== 8756 + dependencies: 8757 + call-bind "^1.0.7" 8758 + define-properties "^1.2.1" 8759 + es-abstract "^1.23.3" 8760 + es-errors "^1.3.0" 8761 + es-shim-unscopables "^1.0.2" 8762 8763 arraybuffer.prototype.slice@^1.0.1: 8764 version "1.0.1" ··· 9283 dependencies: 9284 fill-range "^7.0.1" 9285 9286 + braces@^3.0.3: 9287 + version "3.0.3" 9288 + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" 9289 + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 9290 + dependencies: 9291 + fill-range "^7.1.1" 9292 + 9293 brorand@^1.1.0: 9294 version "1.1.0" 9295 resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" ··· 9811 resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" 9812 integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== 9813 9814 + comment-parser@^1.4.1: 9815 + version "1.4.1" 9816 + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc" 9817 + integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg== 9818 + 9819 commondir@^1.0.1: 9820 version "1.0.1" 9821 resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" ··· 10001 dependencies: 10002 node-fetch "^2.6.12" 10003 10004 + cross-spawn@^7.0.0, cross-spawn@^7.0.3: 10005 version "7.0.3" 10006 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 10007 integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== ··· 10010 shebang-command "^2.0.0" 10011 which "^2.0.1" 10012 10013 + cross-spawn@^7.0.6: 10014 + version "7.0.6" 10015 + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" 10016 + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== 10017 + dependencies: 10018 + path-key "^3.1.0" 10019 + shebang-command "^2.0.0" 10020 + which "^2.0.1" 10021 + 10022 crypto-random-string@^2.0.0: 10023 version "2.0.0" 10024 resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" ··· 10282 dependencies: 10283 ms "2.1.2" 10284 10285 + debug@^3.1.0: 10286 version "3.2.7" 10287 resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 10288 integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== ··· 10300 version "4.4.0" 10301 resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" 10302 integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== 10303 + dependencies: 10304 + ms "^2.1.3" 10305 + 10306 + debug@^4.4.1, debug@^4.4.3: 10307 + version "4.4.3" 10308 + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" 10309 + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== 10310 dependencies: 10311 ms "^2.1.3" 10312 ··· 10530 dependencies: 10531 esutils "^2.0.2" 10532 10533 dom-converter@^0.2.0: 10534 version "0.2.0" 10535 resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" ··· 10785 dependencies: 10786 stackframe "^1.3.4" 10787 10788 + es-abstract@^1.17.5, es-abstract@^1.23.3, es-abstract@^1.23.6, es-abstract@^1.24.1: 10789 + version "1.24.1" 10790 + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.1.tgz#f0c131ed5ea1bb2411134a8dd94def09c46c7899" 10791 + integrity sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw== 10792 + dependencies: 10793 + array-buffer-byte-length "^1.0.2" 10794 + arraybuffer.prototype.slice "^1.0.4" 10795 + available-typed-arrays "^1.0.7" 10796 + call-bind "^1.0.8" 10797 + call-bound "^1.0.4" 10798 + data-view-buffer "^1.0.2" 10799 + data-view-byte-length "^1.0.2" 10800 + data-view-byte-offset "^1.0.1" 10801 + es-define-property "^1.0.1" 10802 + es-errors "^1.3.0" 10803 + es-object-atoms "^1.1.1" 10804 + es-set-tostringtag "^2.1.0" 10805 + es-to-primitive "^1.3.0" 10806 + function.prototype.name "^1.1.8" 10807 + get-intrinsic "^1.3.0" 10808 + get-proto "^1.0.1" 10809 + get-symbol-description "^1.1.0" 10810 + globalthis "^1.0.4" 10811 + gopd "^1.2.0" 10812 + has-property-descriptors "^1.0.2" 10813 + has-proto "^1.2.0" 10814 + has-symbols "^1.1.0" 10815 + hasown "^2.0.2" 10816 + internal-slot "^1.1.0" 10817 + is-array-buffer "^3.0.5" 10818 + is-callable "^1.2.7" 10819 + is-data-view "^1.0.2" 10820 + is-negative-zero "^2.0.3" 10821 + is-regex "^1.2.1" 10822 + is-set "^2.0.3" 10823 + is-shared-array-buffer "^1.0.4" 10824 + is-string "^1.1.1" 10825 + is-typed-array "^1.1.15" 10826 + is-weakref "^1.1.1" 10827 + math-intrinsics "^1.1.0" 10828 + object-inspect "^1.13.4" 10829 + object-keys "^1.1.1" 10830 + object.assign "^4.1.7" 10831 + own-keys "^1.0.1" 10832 + regexp.prototype.flags "^1.5.4" 10833 + safe-array-concat "^1.1.3" 10834 + safe-push-apply "^1.0.0" 10835 + safe-regex-test "^1.1.0" 10836 + set-proto "^1.0.0" 10837 + stop-iteration-iterator "^1.1.0" 10838 + string.prototype.trim "^1.2.10" 10839 + string.prototype.trimend "^1.0.9" 10840 + string.prototype.trimstart "^1.0.8" 10841 + typed-array-buffer "^1.0.3" 10842 + typed-array-byte-length "^1.0.3" 10843 + typed-array-byte-offset "^1.0.4" 10844 + typed-array-length "^1.0.7" 10845 + unbox-primitive "^1.1.0" 10846 + which-typed-array "^1.1.19" 10847 + 10848 es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.3: 10849 version "1.22.1" 10850 resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" ··· 11022 iterator.prototype "^1.1.0" 11023 safe-array-concat "^1.0.0" 11024 11025 + es-iterator-helpers@^1.2.1: 11026 + version "1.2.2" 11027 + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz#d979a9f686e2b0b72f88dbead7229924544720bc" 11028 + integrity sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w== 11029 + dependencies: 11030 + call-bind "^1.0.8" 11031 + call-bound "^1.0.4" 11032 + define-properties "^1.2.1" 11033 + es-abstract "^1.24.1" 11034 + es-errors "^1.3.0" 11035 + es-set-tostringtag "^2.1.0" 11036 + function-bind "^1.1.2" 11037 + get-intrinsic "^1.3.0" 11038 + globalthis "^1.0.4" 11039 + gopd "^1.2.0" 11040 + has-property-descriptors "^1.0.2" 11041 + has-proto "^1.2.0" 11042 + has-symbols "^1.1.0" 11043 + internal-slot "^1.1.0" 11044 + iterator.prototype "^1.1.5" 11045 + safe-array-concat "^1.1.3" 11046 + 11047 es-module-lexer@^1.2.1: 11048 version "1.3.0" 11049 resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" ··· 11082 dependencies: 11083 has "^1.0.3" 11084 11085 + es-shim-unscopables@^1.0.2: 11086 version "1.1.0" 11087 resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" 11088 integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== ··· 11191 resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" 11192 integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== 11193 11194 + eslint-import-context@^0.1.8, eslint-import-context@^0.1.9: 11195 + version "0.1.9" 11196 + resolved "https://registry.yarnpkg.com/eslint-import-context/-/eslint-import-context-0.1.9.tgz#967b0b2f0a90ef4b689125e088f790f0b7756dbe" 11197 + integrity sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg== 11198 dependencies: 11199 + get-tsconfig "^4.10.1" 11200 + stable-hash-x "^0.2.0" 11201 11202 + eslint-import-resolver-typescript@^4.4.4: 11203 + version "4.4.4" 11204 + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz#3e83a9c25f4a053fe20e1b07b47e04e8519a8720" 11205 + integrity sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw== 11206 dependencies: 11207 + debug "^4.4.1" 11208 + eslint-import-context "^0.1.8" 11209 + get-tsconfig "^4.10.1" 11210 + is-bun-module "^2.0.0" 11211 + stable-hash-x "^0.2.0" 11212 + tinyglobby "^0.2.14" 11213 + unrs-resolver "^1.7.11" 11214 11215 "eslint-plugin-bsky-internal@link:./eslint": 11216 version "0.0.0" ··· 11224 escape-string-regexp "^1.0.5" 11225 ignore "^5.0.5" 11226 11227 + eslint-plugin-ft-flow@^2.0.1: 11228 version "2.0.3" 11229 resolved "https://registry.yarnpkg.com/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz#3b3c113c41902bcbacf0e22b536debcfc3c819e8" 11230 integrity sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg== ··· 11232 lodash "^4.17.21" 11233 string-natural-compare "^3.0.1" 11234 11235 + eslint-plugin-import-x@^4.16.1: 11236 + version "4.16.1" 11237 + resolved "https://registry.yarnpkg.com/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz#a96ee1ad5ba6816f9a5573a9617935011a24c4df" 11238 + integrity sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ== 11239 dependencies: 11240 + "@typescript-eslint/types" "^8.35.0" 11241 + comment-parser "^1.4.1" 11242 + debug "^4.4.1" 11243 + eslint-import-context "^0.1.9" 11244 is-glob "^4.0.3" 11245 + minimatch "^9.0.3 || ^10.0.1" 11246 + semver "^7.7.2" 11247 + stable-hash-x "^0.2.0" 11248 + unrs-resolver "^1.9.2" 11249 11250 eslint-plugin-jest@^27.9.0: 11251 version "27.9.0" ··· 11254 dependencies: 11255 "@typescript-eslint/utils" "^5.10.0" 11256 11257 + eslint-plugin-lingui@^0.11.0: 11258 + version "0.11.0" 11259 + resolved "https://registry.yarnpkg.com/eslint-plugin-lingui/-/eslint-plugin-lingui-0.11.0.tgz#e33a4fe83698bb4cdfbfa816391fb79b68e6c026" 11260 + integrity sha512-O2Ixoapt5fa4VKZJgXhVwb6BHnzByIUDNMfZOhHWGMYk40GfGCho4MUfspLVrHAFLimgBPKXtCcJ8GC4YNZmfg== 11261 dependencies: 11262 + "@typescript-eslint/utils" "^8.0.0" 11263 + micromatch "^4.0.0" 11264 11265 eslint-plugin-react-compiler@^19.1.0-rc.2: 11266 version "19.1.0-rc.2" ··· 11279 resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" 11280 integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== 11281 11282 + eslint-plugin-react-hooks@^7.0.1: 11283 + version "7.0.1" 11284 + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz#66e258db58ece50723ef20cc159f8aa908219169" 11285 + integrity sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA== 11286 + dependencies: 11287 + "@babel/core" "^7.24.4" 11288 + "@babel/parser" "^7.24.4" 11289 + hermes-parser "^0.25.1" 11290 + zod "^3.25.0 || ^4.0.0" 11291 + zod-validation-error "^3.5.0 || ^4.0.0" 11292 + 11293 + eslint-plugin-react-native-a11y@^3.5.1: 11294 + version "3.5.1" 11295 + resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-a11y/-/eslint-plugin-react-native-a11y-3.5.1.tgz#370339a308cace74409bfe582e9cfe627921f38a" 11296 + integrity sha512-vqnXZpAiov0lxYNfEYgwABpkiBYRrt0dbtOafPkw6QaFeA0uZ+s3w9opeEMoFmV36WFxLiCxHb9fvOJ+EUc2xQ== 11297 dependencies: 11298 "@babel/runtime" "^7.15.4" 11299 ast-types-flow "^0.0.7" ··· 11312 "@babel/traverse" "^7.7.4" 11313 eslint-plugin-react-native-globals "^0.1.1" 11314 11315 + eslint-plugin-react-native@^5.0.0: 11316 + version "5.0.0" 11317 + resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-5.0.0.tgz#2ee990ba4967c557183b31121578547fb5c02d5d" 11318 + integrity sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q== 11319 + dependencies: 11320 + eslint-plugin-react-native-globals "^0.1.1" 11321 + 11322 + eslint-plugin-react@^7.30.1: 11323 version "7.33.2" 11324 resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" 11325 integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== ··· 11341 semver "^6.3.1" 11342 string.prototype.matchall "^4.0.8" 11343 11344 + eslint-plugin-react@^7.37.5: 11345 + version "7.37.5" 11346 + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" 11347 + integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== 11348 + dependencies: 11349 + array-includes "^3.1.8" 11350 + array.prototype.findlast "^1.2.5" 11351 + array.prototype.flatmap "^1.3.3" 11352 + array.prototype.tosorted "^1.1.4" 11353 + doctrine "^2.1.0" 11354 + es-iterator-helpers "^1.2.1" 11355 + estraverse "^5.3.0" 11356 + hasown "^2.0.2" 11357 + jsx-ast-utils "^2.4.1 || ^3.0.0" 11358 + minimatch "^3.1.2" 11359 + object.entries "^1.1.9" 11360 + object.fromentries "^2.0.8" 11361 + object.values "^1.2.1" 11362 + prop-types "^15.8.1" 11363 + resolve "^2.0.0-next.5" 11364 + semver "^6.3.1" 11365 + string.prototype.matchall "^4.0.12" 11366 + string.prototype.repeat "^1.0.0" 11367 + 11368 + eslint-plugin-simple-import-sort@^12.1.1: 11369 + version "12.1.1" 11370 + resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz#e64bfdaf91c5b98a298619aa634a9f7aa43b709e" 11371 + integrity sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA== 11372 11373 eslint-scope@5.1.1, eslint-scope@^5.1.1: 11374 version "5.1.1" ··· 11378 esrecurse "^4.3.0" 11379 estraverse "^4.1.1" 11380 11381 + eslint-scope@^8.4.0: 11382 + version "8.4.0" 11383 + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" 11384 + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== 11385 dependencies: 11386 esrecurse "^4.3.0" 11387 estraverse "^5.2.0" ··· 11391 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" 11392 integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== 11393 11394 + eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: 11395 version "3.4.3" 11396 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" 11397 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== 11398 11399 + eslint-visitor-keys@^4.2.1: 11400 + version "4.2.1" 11401 + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" 11402 + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== 11403 + 11404 + eslint@^9.39.2: 11405 + version "9.39.2" 11406 + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c" 11407 + integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== 11408 dependencies: 11409 + "@eslint-community/eslint-utils" "^4.8.0" 11410 + "@eslint-community/regexpp" "^4.12.1" 11411 + "@eslint/config-array" "^0.21.1" 11412 + "@eslint/config-helpers" "^0.4.2" 11413 + "@eslint/core" "^0.17.0" 11414 + "@eslint/eslintrc" "^3.3.1" 11415 + "@eslint/js" "9.39.2" 11416 + "@eslint/plugin-kit" "^0.4.1" 11417 + "@humanfs/node" "^0.16.6" 11418 "@humanwhocodes/module-importer" "^1.0.1" 11419 + "@humanwhocodes/retry" "^0.4.2" 11420 + "@types/estree" "^1.0.6" 11421 ajv "^6.12.4" 11422 chalk "^4.0.0" 11423 + cross-spawn "^7.0.6" 11424 debug "^4.3.2" 11425 escape-string-regexp "^4.0.0" 11426 + eslint-scope "^8.4.0" 11427 + eslint-visitor-keys "^4.2.1" 11428 + espree "^10.4.0" 11429 + esquery "^1.5.0" 11430 esutils "^2.0.2" 11431 fast-deep-equal "^3.1.3" 11432 + file-entry-cache "^8.0.0" 11433 find-up "^5.0.0" 11434 glob-parent "^6.0.2" 11435 ignore "^5.2.0" 11436 imurmurhash "^0.1.4" 11437 is-glob "^4.0.0" 11438 json-stable-stringify-without-jsonify "^1.0.1" 11439 lodash.merge "^4.6.2" 11440 minimatch "^3.1.2" 11441 natural-compare "^1.4.0" 11442 optionator "^0.9.3" 11443 11444 + espree@^10.0.1, espree@^10.4.0: 11445 + version "10.4.0" 11446 + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" 11447 + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== 11448 dependencies: 11449 + acorn "^8.15.0" 11450 acorn-jsx "^5.3.2" 11451 + eslint-visitor-keys "^4.2.1" 11452 11453 esprima@^4.0.0, esprima@^4.0.1: 11454 version "4.0.1" 11455 resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 11456 integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 11457 11458 + esquery@^1.5.0: 11459 + version "1.7.0" 11460 + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" 11461 + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== 11462 dependencies: 11463 estraverse "^5.1.0" 11464 ··· 12144 dependencies: 12145 escape-string-regexp "^1.0.5" 12146 12147 + file-entry-cache@^8.0.0: 12148 + version "8.0.0" 12149 + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" 12150 + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== 12151 dependencies: 12152 + flat-cache "^4.0.0" 12153 12154 file-loader@6.2.0: 12155 version "6.2.0" ··· 12177 version "7.0.1" 12178 resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 12179 integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 12180 + dependencies: 12181 + to-regex-range "^5.0.1" 12182 + 12183 + fill-range@^7.1.1: 12184 + version "7.1.1" 12185 + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" 12186 + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 12187 dependencies: 12188 to-regex-range "^5.0.1" 12189 ··· 12264 dependencies: 12265 micromatch "^4.0.2" 12266 12267 + flat-cache@^4.0.0: 12268 + version "4.0.1" 12269 + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" 12270 + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== 12271 dependencies: 12272 + flatted "^3.2.9" 12273 + keyv "^4.5.4" 12274 12275 + flatted@^3.2.9: 12276 + version "3.3.3" 12277 + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" 12278 + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== 12279 12280 flow-enums-runtime@^0.0.6: 12281 version "0.0.6" ··· 12509 es-errors "^1.3.0" 12510 get-intrinsic "^1.2.6" 12511 12512 + get-tsconfig@^4.10.1: 12513 + version "4.13.0" 12514 + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" 12515 + integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ== 12516 + dependencies: 12517 + resolve-pkg-maps "^1.0.0" 12518 + 12519 getenv@^1.0.0: 12520 version "1.0.0" 12521 resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31" ··· 12616 resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 12617 integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 12618 12619 + globals@^14.0.0: 12620 + version "14.0.0" 12621 + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" 12622 + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== 12623 + 12624 + globals@^17.0.0: 12625 + version "17.0.0" 12626 + resolved "https://registry.yarnpkg.com/globals/-/globals-17.0.0.tgz#a4196d9cfeb4d627ba165b4647b1f5853bf90a30" 12627 + integrity sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw== 12628 12629 globalthis@^1.0.2, globalthis@^1.0.3: 12630 version "1.0.3" ··· 13109 resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" 13110 integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== 13111 13112 + ignore@^7.0.5: 13113 + version "7.0.5" 13114 + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" 13115 + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== 13116 + 13117 image-size@^1.0.2: 13118 version "1.0.2" 13119 resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.2.tgz#d778b6d0ab75b2737c1556dd631652eb963bc486" ··· 13349 call-bound "^1.0.3" 13350 has-tostringtag "^1.0.2" 13351 13352 + is-bun-module@^2.0.0: 13353 + version "2.0.0" 13354 + resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-2.0.0.tgz#4d7859a87c0fcac950c95e666730e745eae8bddd" 13355 + integrity sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ== 13356 + dependencies: 13357 + semver "^7.7.1" 13358 + 13359 is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: 13360 version "1.2.7" 13361 resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" ··· 13374 integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== 13375 dependencies: 13376 has "^1.0.3" 13377 13378 is-data-view@^1.0.1, is-data-view@^1.0.2: 13379 version "1.0.2" ··· 13485 resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" 13486 integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== 13487 13488 + is-negative-zero@^2.0.3: 13489 + version "2.0.3" 13490 + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" 13491 + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== 13492 + 13493 is-number-object@^1.0.4: 13494 version "1.0.7" 13495 resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" ··· 13529 dependencies: 13530 path-is-inside "^1.0.2" 13531 13532 is-plain-obj@^2.1.0: 13533 version "2.1.0" 13534 resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" ··· 13668 dependencies: 13669 call-bind "^1.0.2" 13670 13671 + is-weakref@^1.1.0, is-weakref@^1.1.1: 13672 version "1.1.1" 13673 resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" 13674 integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== ··· 13782 has-tostringtag "^1.0.0" 13783 reflect.getprototypeof "^1.0.3" 13784 13785 + iterator.prototype@^1.1.5: 13786 + version "1.1.5" 13787 + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" 13788 + integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== 13789 + dependencies: 13790 + define-data-property "^1.1.4" 13791 + es-object-atoms "^1.0.0" 13792 + get-intrinsic "^1.2.6" 13793 + get-proto "^1.0.0" 13794 + has-symbols "^1.1.0" 13795 + set-function-name "^2.0.2" 13796 + 13797 jackspeak@^2.3.6: 13798 version "2.3.6" 13799 resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" ··· 14394 dependencies: 14395 argparse "^2.0.1" 14396 14397 + js-yaml@^4.1.1: 14398 + version "4.1.1" 14399 + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" 14400 + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== 14401 + dependencies: 14402 + argparse "^2.0.1" 14403 + 14404 jsc-safe-url@^0.2.2, jsc-safe-url@^0.2.4: 14405 version "0.2.4" 14406 resolved "https://registry.yarnpkg.com/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz#141c14fbb43791e88d5dc64e85a374575a83477a" ··· 14453 resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" 14454 integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== 14455 14456 + json-buffer@3.0.1: 14457 + version "3.0.1" 14458 + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" 14459 + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== 14460 + 14461 json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: 14462 version "2.3.1" 14463 resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" ··· 14496 jsonify "^0.0.1" 14497 object-keys "^1.1.1" 14498 14499 json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: 14500 version "2.2.3" 14501 resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" ··· 14539 asn1.js "^5.0.1" 14540 bn.js "^4.11.8" 14541 elliptic "^6.4.1" 14542 + 14543 + keyv@^4.5.4: 14544 + version "4.5.4" 14545 + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" 14546 + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== 14547 + dependencies: 14548 + json-buffer "3.0.1" 14549 14550 klaw-sync@^6.0.0: 14551 version "6.0.0" ··· 15263 braces "^3.0.2" 15264 picomatch "^2.3.1" 15265 15266 + micromatch@^4.0.0: 15267 + version "4.0.8" 15268 + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" 15269 + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== 15270 + dependencies: 15271 + braces "^3.0.3" 15272 + picomatch "^2.3.1" 15273 + 15274 mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": 15275 version "1.52.0" 15276 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" ··· 15330 resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" 15331 integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== 15332 15333 + minimatch@^10.1.1, "minimatch@^9.0.3 || ^10.0.1": 15334 version "10.1.1" 15335 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" 15336 integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== 15337 dependencies: 15338 "@isaacs/brace-expansion" "^5.0.0" 15339 15340 + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: 15341 version "3.1.2" 15342 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 15343 integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== ··· 15351 dependencies: 15352 brace-expansion "^2.0.1" 15353 15354 + minimatch@^9.0.0, minimatch@^9.0.5: 15355 version "9.0.5" 15356 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" 15357 integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== ··· 15484 resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" 15485 integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== 15486 15487 + napi-postinstall@^0.3.0: 15488 + version "0.3.4" 15489 + resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.4.tgz#7af256d6588b5f8e952b9190965d6b019653bbb9" 15490 + integrity sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ== 15491 + 15492 natural-compare@^1.4.0: 15493 version "1.4.0" 15494 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" ··· 15666 resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" 15667 integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== 15668 15669 + object-inspect@^1.13.3, object-inspect@^1.13.4: 15670 version "1.13.4" 15671 resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" 15672 integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== ··· 15715 define-properties "^1.1.4" 15716 es-abstract "^1.20.4" 15717 15718 + object.entries@^1.1.9: 15719 + version "1.1.9" 15720 + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" 15721 + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== 15722 + dependencies: 15723 + call-bind "^1.0.8" 15724 + call-bound "^1.0.4" 15725 + define-properties "^1.2.1" 15726 + es-object-atoms "^1.1.1" 15727 + 15728 object.fromentries@^2.0.6: 15729 version "2.0.6" 15730 resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" ··· 15744 es-abstract "^1.23.2" 15745 es-object-atoms "^1.0.0" 15746 15747 object.hasown@^1.1.2: 15748 version "1.1.2" 15749 resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" ··· 15761 define-properties "^1.1.4" 15762 es-abstract "^1.20.4" 15763 15764 + object.values@^1.2.1: 15765 version "1.2.1" 15766 resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" 15767 integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== ··· 17702 define-properties "^1.2.0" 17703 set-function-name "^2.0.0" 17704 17705 + regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: 17706 version "1.5.4" 17707 resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" 17708 integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== ··· 17846 dependencies: 17847 global-dirs "^0.1.1" 17848 17849 + resolve-pkg-maps@^1.0.0: 17850 + version "1.0.0" 17851 + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" 17852 + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== 17853 + 17854 resolve-workspace-root@^2.0.0: 17855 version "2.0.0" 17856 resolved "https://registry.yarnpkg.com/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz#a0098daa0067cd0efa6eb525c57c8fb4a61e78f8" ··· 17875 path-parse "^1.0.7" 17876 supports-preserve-symlinks-flag "^1.0.0" 17877 17878 resolve@^1.22.8: 17879 version "1.22.8" 17880 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" ··· 17890 integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== 17891 dependencies: 17892 is-core-module "^2.9.0" 17893 + path-parse "^1.0.7" 17894 + supports-preserve-symlinks-flag "^1.0.0" 17895 + 17896 + resolve@^2.0.0-next.5: 17897 + version "2.0.0-next.5" 17898 + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" 17899 + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== 17900 + dependencies: 17901 + is-core-module "^2.13.0" 17902 path-parse "^1.0.7" 17903 supports-preserve-symlinks-flag "^1.0.0" 17904 ··· 18175 version "7.6.2" 18176 resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" 18177 integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== 18178 + 18179 + semver@^7.7.1, semver@^7.7.2, semver@^7.7.3: 18180 + version "7.7.3" 18181 + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" 18182 + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== 18183 18184 send@0.18.0: 18185 version "0.18.0" ··· 18678 resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 18679 integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== 18680 18681 + stable-hash-x@^0.2.0: 18682 + version "0.2.0" 18683 + resolved "https://registry.yarnpkg.com/stable-hash-x/-/stable-hash-x-0.2.0.tgz#dfd76bfa5d839a7470125c6a6b3c8b22061793e9" 18684 + integrity sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ== 18685 + 18686 stable@^0.1.8: 18687 version "0.1.8" 18688 resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" ··· 18784 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 18785 integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== 18786 18787 + stop-iteration-iterator@^1.1.0: 18788 + version "1.1.0" 18789 + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" 18790 + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== 18791 + dependencies: 18792 + es-errors "^1.3.0" 18793 + internal-slot "^1.1.0" 18794 + 18795 stream-browserify@3.0.0: 18796 version "3.0.0" 18797 resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" ··· 18863 emoji-regex "^9.2.2" 18864 strip-ansi "^7.0.1" 18865 18866 + string.prototype.matchall@^4.0.12: 18867 + version "4.0.12" 18868 + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" 18869 + integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== 18870 + dependencies: 18871 + call-bind "^1.0.8" 18872 + call-bound "^1.0.3" 18873 + define-properties "^1.2.1" 18874 + es-abstract "^1.23.6" 18875 + es-errors "^1.3.0" 18876 + es-object-atoms "^1.0.0" 18877 + get-intrinsic "^1.2.6" 18878 + gopd "^1.2.0" 18879 + has-symbols "^1.1.0" 18880 + internal-slot "^1.1.0" 18881 + regexp.prototype.flags "^1.5.3" 18882 + set-function-name "^2.0.2" 18883 + side-channel "^1.1.0" 18884 + 18885 string.prototype.matchall@^4.0.8: 18886 version "4.0.8" 18887 resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" ··· 18896 regexp.prototype.flags "^1.4.3" 18897 side-channel "^1.0.4" 18898 18899 + string.prototype.repeat@^1.0.0: 18900 + version "1.0.0" 18901 + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" 18902 + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== 18903 + dependencies: 18904 + define-properties "^1.1.3" 18905 + es-abstract "^1.17.5" 18906 + 18907 string.prototype.trim@^1.2.10: 18908 version "1.2.10" 18909 resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" ··· 18953 define-properties "^1.2.0" 18954 es-abstract "^1.22.1" 18955 18956 + string.prototype.trimend@^1.0.9: 18957 version "1.0.9" 18958 resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" 18959 integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== ··· 19031 integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== 19032 dependencies: 19033 ansi-regex "^6.0.1" 19034 19035 strip-bom@^4.0.0: 19036 version "4.0.0" ··· 19283 dependencies: 19284 utrie "^1.0.2" 19285 19286 thenify-all@^1.0.0: 19287 version "1.6.0" 19288 resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" ··· 19326 resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" 19327 integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== 19328 19329 + tinyglobby@^0.2.11, tinyglobby@^0.2.14, tinyglobby@^0.2.15: 19330 version "0.2.15" 19331 resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" 19332 integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== ··· 19444 resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.0.tgz#709c6f2076e511a81557f3d07a0cbd566ae8195c" 19445 integrity sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ== 19446 19447 + ts-api-utils@^2.4.0: 19448 + version "2.4.0" 19449 + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" 19450 + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== 19451 + 19452 ts-interface-checker@^0.1.9: 19453 version "0.1.13" 19454 resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" ··· 19478 resolved "https://registry.yarnpkg.com/ts-plugin-sort-import-suggestions/-/ts-plugin-sort-import-suggestions-1.0.4.tgz#d1ed6c235feb8c8bb8b34c625ea75b46e3e62925" 19479 integrity sha512-85n5lm2OQQ+b7aRNK9omU1gmjMNXRsgeLwojm5u4OSY5sVBkAHTcgMQPEeHMNlyyfFW0uXnwgqAU0pNfhD96Bw== 19480 19481 tslib@^1.8.1, tslib@^1.9.0: 19482 version "1.14.1" 19483 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" ··· 19518 version "4.0.8" 19519 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 19520 integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 19521 19522 type-fest@^0.21.3: 19523 version "0.21.3" ··· 19642 integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== 19643 optionalDependencies: 19644 rxjs "^7.5.2" 19645 + 19646 + typescript-eslint@^8.53.0: 19647 + version "8.53.0" 19648 + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.53.0.tgz#c35ca6403cd381753aee325f67e10d6101d55f04" 19649 + integrity sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw== 19650 + dependencies: 19651 + "@typescript-eslint/eslint-plugin" "8.53.0" 19652 + "@typescript-eslint/parser" "8.53.0" 19653 + "@typescript-eslint/typescript-estree" "8.53.0" 19654 + "@typescript-eslint/utils" "8.53.0" 19655 19656 typescript@^5.9.2: 19657 version "5.9.2" ··· 19812 resolved "https://registry.yarnpkg.com/unraw/-/unraw-3.0.0.tgz#73443ed70d2ab09ccbac2b00525602d5991fbbe3" 19813 integrity sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg== 19814 19815 + unrs-resolver@^1.7.11, unrs-resolver@^1.9.2: 19816 + version "1.11.1" 19817 + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" 19818 + integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== 19819 + dependencies: 19820 + napi-postinstall "^0.3.0" 19821 + optionalDependencies: 19822 + "@unrs/resolver-binding-android-arm-eabi" "1.11.1" 19823 + "@unrs/resolver-binding-android-arm64" "1.11.1" 19824 + "@unrs/resolver-binding-darwin-arm64" "1.11.1" 19825 + "@unrs/resolver-binding-darwin-x64" "1.11.1" 19826 + "@unrs/resolver-binding-freebsd-x64" "1.11.1" 19827 + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.11.1" 19828 + "@unrs/resolver-binding-linux-arm-musleabihf" "1.11.1" 19829 + "@unrs/resolver-binding-linux-arm64-gnu" "1.11.1" 19830 + "@unrs/resolver-binding-linux-arm64-musl" "1.11.1" 19831 + "@unrs/resolver-binding-linux-ppc64-gnu" "1.11.1" 19832 + "@unrs/resolver-binding-linux-riscv64-gnu" "1.11.1" 19833 + "@unrs/resolver-binding-linux-riscv64-musl" "1.11.1" 19834 + "@unrs/resolver-binding-linux-s390x-gnu" "1.11.1" 19835 + "@unrs/resolver-binding-linux-x64-gnu" "1.11.1" 19836 + "@unrs/resolver-binding-linux-x64-musl" "1.11.1" 19837 + "@unrs/resolver-binding-wasm32-wasi" "1.11.1" 19838 + "@unrs/resolver-binding-win32-arm64-msvc" "1.11.1" 19839 + "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" 19840 + "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" 19841 + 19842 update-browserslist-db@^1.0.11: 19843 version "1.0.11" 19844 resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" ··· 20384 gopd "^1.2.0" 20385 has-tostringtag "^1.0.2" 20386 20387 + which-typed-array@^1.1.19: 20388 + version "1.1.20" 20389 + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" 20390 + integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== 20391 + dependencies: 20392 + available-typed-arrays "^1.0.7" 20393 + call-bind "^1.0.8" 20394 + call-bound "^1.0.4" 20395 + for-each "^0.3.5" 20396 + get-proto "^1.0.1" 20397 + gopd "^1.2.0" 20398 + has-tostringtag "^1.0.2" 20399 + 20400 which-typed-array@^1.1.9: 20401 version "1.1.9" 20402 resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" ··· 20663 resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.3.0.tgz#2cfe81b62d044e0453d1aa3ae7c32a2f36dde9af" 20664 integrity sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw== 20665 20666 + "zod-validation-error@^3.5.0 || ^4.0.0": 20667 + version "4.0.2" 20668 + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918" 20669 + integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== 20670 + 20671 zod@3.23.8, zod@^3.14.2, zod@^3.20.2, zod@^3.22.4, zod@^3.23.8: 20672 version "3.23.8" 20673 resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" 20674 integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== 20675 + 20676 + "zod@^3.25.0 || ^4.0.0": 20677 + version "4.3.5" 20678 + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a" 20679 + integrity sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==