Bluesky app fork with some witchin' additions 💫

Merge branch 'main' of https://github.com/bluesky-social/social-app

Changed files
+1124 -448
.github
bskyembed
bskylink
bskyogcard
src
modules
expo-emoji-picker
scripts
src
alf
components
ContextMenu
Post
Embed
VideoEmbed
VideoEmbedInner
web-controls
PostControls
Select
Tooltip
dialogs
EmailDialog
screens
dms
hooks
useOnGesture
icons
moderation
verification
lib
locale
locales
platform
screens
Login
Messages
Onboarding
Profile
Search
Settings
AppIconSettings
components
state
cache
global-gesture-events
queries
storage
view
com
screens
-4
.github/workflows/build-submit-android.yml
··· 52 52 distribution: 'temurin' 53 53 java-version: '17' 54 54 55 - - name: "Use upgraded MMKV for Fabric" 56 - run: | 57 - sed -i 's/"react-native-mmkv": "\^2\.12\.2"/"react-native-mmkv": "^3.3.0"/' package.json 58 - 59 55 - name: ⚙️ Install dependencies 60 56 run: yarn install 61 57
+1 -13
.github/workflows/bundle-deploy-eas-update.yml
··· 5 5 push: 6 6 branches: 7 7 - main 8 - - hailey/eas-fab 9 8 workflow_dispatch: 10 9 inputs: 11 10 channel: ··· 119 118 120 119 - name: 🏗️ Create Bundle 121 120 if: ${{ !steps.fingerprint.outputs.includes-changes }} 122 - run: | 123 - SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export-ios 124 - mv ./dist ./ios-dist 125 - sed -i 's/"react-native-mmkv": "\^2\.12\.2"/"react-native-mmkv": "^3.3.0"/' package.json 126 - yarn install 127 - SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export-android 128 - mv ./dist ./android-dist 129 - 121 + run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export 130 122 131 123 - name: 📦 Package Bundle and 🚀 Deploy 132 124 if: ${{ !steps.fingerprint.outputs.includes-changes }} ··· 282 274 with: 283 275 distribution: 'temurin' 284 276 java-version: '17' 285 - 286 - - name: "Use upgraded MMKV for Fabric" 287 - run: | 288 - sed -i 's/"react-native-mmkv": "\^2\.12\.2"/"react-native-mmkv": "^3.3.0"/' package.json 289 277 290 278 - name: ⚙️ Install dependencies 291 279 run: yarn install
+1 -1
app.config.js
··· 221 221 compileSdkVersion: 35, 222 222 targetSdkVersion: 35, 223 223 buildToolsVersion: '35.0.0', 224 - newArchEnabled: true, 224 + newArchEnabled: false, 225 225 }, 226 226 }, 227 227 ],
+1 -3
bskyembed/tailwind.config.cjs
··· 1 1 /** @type {import('tailwindcss').Config} */ 2 2 module.exports = { 3 3 content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 - darkMode: ['variant', [ 5 - '&:is(.dark *):not(:is(.dark .light *))', 6 - ]], 4 + darkMode: ['variant', ['&:is(.dark *):not(:is(.dark .light *))']], 7 5 theme: { 8 6 extend: { 9 7 colors: {
-1
bskyembed/tsconfig.snippet.json
··· 1 - 2 1 { 3 2 "compilerOptions": { 4 3 "target": "ES5",
+11 -8
bskylink/src/db/index.ts
··· 1 1 import assert from 'assert' 2 2 import { 3 3 Kysely, 4 - KyselyPlugin, 4 + type KyselyPlugin, 5 5 Migrator, 6 - PluginTransformQueryArgs, 7 - PluginTransformResultArgs, 6 + type PluginTransformQueryArgs, 7 + type PluginTransformResultArgs, 8 8 PostgresDialect, 9 - QueryResult, 10 - RootOperationNode, 11 - UnknownRow, 9 + type QueryResult, 10 + type RootOperationNode, 11 + type UnknownRow, 12 12 } from 'kysely' 13 13 import {default as Pg} from 'pg' 14 14 15 15 import {dbLogger as log} from '../logger.js' 16 16 import {default as migrations} from './migrations/index.js' 17 17 import {DbMigrationProvider} from './migrations/provider.js' 18 - import {DbSchema} from './schema.js' 18 + import {type DbSchema} from './schema.js' 19 19 20 20 export class Database { 21 21 migrator: Migrator 22 22 destroyed = false 23 23 24 - constructor(public db: Kysely<DbSchema>, public cfg: PgConfig) { 24 + constructor( 25 + public db: Kysely<DbSchema>, 26 + public cfg: PgConfig, 27 + ) { 25 28 this.migrator = new Migrator({ 26 29 db, 27 30 migrationTableSchema: cfg.schema,
+7 -4
bskylink/src/index.ts
··· 1 1 import events from 'node:events' 2 - import http from 'node:http' 2 + import type http from 'node:http' 3 3 4 4 import cors from 'cors' 5 5 import express from 'express' 6 - import {createHttpTerminator, HttpTerminator} from 'http-terminator' 6 + import {createHttpTerminator, type HttpTerminator} from 'http-terminator' 7 7 8 - import {Config} from './config.js' 8 + import {type Config} from './config.js' 9 9 import {AppContext} from './context.js' 10 10 import {default as routes, errorHandler} from './routes/index.js' 11 11 ··· 17 17 public server?: http.Server 18 18 private terminator?: HttpTerminator 19 19 20 - constructor(public app: express.Application, public ctx: AppContext) {} 20 + constructor( 21 + public app: express.Application, 22 + public ctx: AppContext, 23 + ) {} 21 24 22 25 static async create(cfg: Config): Promise<LinkService> { 23 26 let app = express()
+7 -4
bskyogcard/src/index.ts
··· 1 1 import events from 'node:events' 2 - import http from 'node:http' 2 + import type http from 'node:http' 3 3 4 4 import express from 'express' 5 - import {createHttpTerminator, HttpTerminator} from 'http-terminator' 5 + import {createHttpTerminator, type HttpTerminator} from 'http-terminator' 6 6 7 - import {Config} from './config.js' 7 + import {type Config} from './config.js' 8 8 import {AppContext} from './context.js' 9 9 import {default as routes, errorHandler} from './routes/index.js' 10 10 ··· 15 15 public server?: http.Server 16 16 private terminator?: HttpTerminator 17 17 18 - constructor(public app: express.Application, public ctx: AppContext) {} 18 + constructor( 19 + public app: express.Application, 20 + public ctx: AppContext, 21 + ) {} 19 22 20 23 static async create(cfg: Config): Promise<CardService> { 21 24 let app = express()
+1 -1
modules/expo-emoji-picker/src/EmojiPicker.android.tsx
··· 12 12 flex: 1, 13 13 width: '100%', 14 14 backgroundColor: scheme === 'dark' ? '#000' : '#fff', 15 - } as const), 15 + }) as const, 16 16 [scheme], 17 17 ) 18 18
+2 -3
package.json
··· 61 61 "intl:push": "crowdin push translations --verbose -b main", 62 62 "nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android", 63 63 "update-extensions": "bash scripts/updateExtensions.sh", 64 - "export-ios": "npx expo export --platform ios --dump-sourcemap && yarn upload-native-sourcemaps", 65 - "export-android": "npx expo export --platform android --dump-sourcemap && yarn upload-native-sourcemaps", 64 + "export": "npx expo export --dump-sourcemap && yarn upload-native-sourcemaps", 66 65 "upload-native-sourcemaps": "npx sentry-expo-upload-sourcemaps dist", 67 66 "make-deploy-bundle": "bash scripts/bundleUpdate.sh", 68 67 "generate-webpack-stats-file": "EXPO_PUBLIC_GENERATE_STATS=1 yarn build-web", ··· 266 265 "lint-staged": "^13.2.3", 267 266 "lockfile-lint": "^4.14.0", 268 267 "metro-react-native-babel-preset": "^0.77.0", 269 - "prettier": "^2.8.3", 268 + "prettier": "^3.6.0", 270 269 "react-native-dotenv": "^3.4.11", 271 270 "react-refresh": "^0.14.0", 272 271 "svgo": "^3.3.2",
+9 -17
scripts/bundleUpdate.js
··· 3 3 const fsp = fs.promises 4 4 const path = require('path') 5 5 6 - const IOS_DIST_DIR = './ios-dist' 7 - const ANDROID_DIST_DIR = './android-dist' 8 - 6 + const DIST_DIR = './dist' 9 7 const BUNDLES_DIR = '/_expo/static/js' 10 - 11 - const IOS_BUNDLE_DIR = path.join(IOS_DIST_DIR, BUNDLES_DIR, '/ios') 12 - const ANDROID_BUNDLE_DIR = path.join(ANDROID_DIST_DIR, BUNDLES_DIR, '/android') 13 - 14 - const IOS_METADATA_PATH = path.join(IOS_DIST_DIR, '/metadata.json') 15 - const ANDROID_METADATA_PATH = path.join(ANDROID_DIST_DIR, '/metadata.json') 16 - 8 + const IOS_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/ios') 9 + const ANDROID_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/android') 10 + const METADATA_PATH = path.join(DIST_DIR, '/metadata.json') 17 11 const DEST_DIR = './bundleTempDir' 18 12 19 13 // Weird, don't feel like figuring out _why_ it wants this 20 - const IOS_METADATA = require(`../${IOS_METADATA_PATH}`) 21 - const ANDROID_METADATA = require(`../${ANDROID_METADATA_PATH}`) 22 - 23 - const IOS_METADATA_ASSETS = IOS_METADATA.fileMetadata.ios.assets 24 - const ANDROID_METADATA_ASSETS = ANDROID_METADATA.fileMetadata.android.assets 14 + const METADATA = require(`../${METADATA_PATH}`) 15 + const IOS_METADATA_ASSETS = METADATA.fileMetadata.ios.assets 16 + const ANDROID_METADATA_ASSETS = METADATA.fileMetadata.android.assets 25 17 26 18 const getMd5 = async path => { 27 19 return new Promise(res => { ··· 68 60 69 61 console.log('Getting ios asset md5s and moving them...') 70 62 for (const asset of IOS_METADATA_ASSETS) { 71 - const currPath = path.join(IOS_DIST_DIR, asset.path) 63 + const currPath = path.join(DIST_DIR, asset.path) 72 64 const md5 = await getMd5(currPath) 73 65 const withExtPath = `assets/${md5}.${asset.ext}` 74 66 iosAssets.push(withExtPath) ··· 77 69 78 70 console.log('Getting android asset md5s and moving them...') 79 71 for (const asset of ANDROID_METADATA_ASSETS) { 80 - const currPath = path.join(ANDROID_DIST_DIR, asset.path) 72 + const currPath = path.join(DIST_DIR, asset.path) 81 73 const md5 = await getMd5(currPath) 82 74 const withExtPath = `assets/${md5}.${asset.ext}` 83 75 androidAssets.push(withExtPath)
+3 -4
scripts/post-web-build.js
··· 9 9 'scripts.html', 10 10 ) 11 11 12 - const {entrypoints} = require(path.join( 13 - projectRoot, 14 - 'web-build/asset-manifest.json', 15 - )) 12 + const {entrypoints} = require( 13 + path.join(projectRoot, 'web-build/asset-manifest.json'), 14 + ) 16 15 17 16 console.log(`Found ${entrypoints.length} entrypoints`) 18 17 console.log(`Writing ${templateFile}`)
+8 -5
src/App.native.tsx
··· 33 33 ensureGeolocationResolved, 34 34 Provider as GeolocationProvider, 35 35 } from '#/state/geolocation' 36 + import {GlobalGestureEventsProvider} from '#/state/global-gesture-events' 36 37 import {Provider as HomeBadgeProvider} from '#/state/home-badge' 37 38 import {Provider as InvitesStateProvider} from '#/state/invites' 38 39 import {Provider as LightboxStateProvider} from '#/state/lightbox' ··· 154 155 <HideBottomBarBorderProvider> 155 156 <GestureHandlerRootView 156 157 style={s.h100pct}> 157 - <IntentDialogProvider> 158 - <TestCtrls /> 159 - <Shell /> 160 - <NuxDialogs /> 161 - </IntentDialogProvider> 158 + <GlobalGestureEventsProvider> 159 + <IntentDialogProvider> 160 + <TestCtrls /> 161 + <Shell /> 162 + <NuxDialogs /> 163 + </IntentDialogProvider> 164 + </GlobalGestureEventsProvider> 162 165 </GestureHandlerRootView> 163 166 </HideBottomBarBorderProvider> 164 167 </ServiceAccountManager>
+4
src/alf/atoms.ts
··· 983 983 transition_none: web({ 984 984 transitionProperty: 'none', 985 985 }), 986 + transition_timing_default: web({ 987 + transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)', 988 + transitionDuration: '100ms', 989 + }), 986 990 transition_all: web({ 987 991 transitionProperty: 'all', 988 992 transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
+3 -3
src/components/ContextMenu/index.tsx
··· 190 190 if (item) playHaptic('Light') 191 191 setHoveredMenuItem(item) 192 192 }, 193 - } satisfies ContextType), 193 + }) satisfies ContextType, 194 194 [ 195 195 measurement, 196 196 setMeasurement, ··· 710 710 const xOffset = position 711 711 ? position.x 712 712 : align === 'left' 713 - ? measurement.x 714 - : measurement.x + measurement.width - layout.width 713 + ? measurement.x 714 + : measurement.x + measurement.width - layout.width 715 715 716 716 registerHoverable( 717 717 id,
+4 -4
src/components/Link.tsx
··· 100 100 return typeof to === 'string' 101 101 ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) 102 102 : to.screen 103 - ? router.matchName(to.screen)?.build(to.params) 104 - : to.href 105 - ? convertBskyAppUrlIfNeeded(sanitizeUrl(to.href)) 106 - : undefined 103 + ? router.matchName(to.screen)?.build(to.params) 104 + : to.href 105 + ? convertBskyAppUrlIfNeeded(sanitizeUrl(to.href)) 106 + : undefined 107 107 }, [to]) 108 108 109 109 if (!href) {
+3 -3
src/components/Post/Embed/ImageEmbed.tsx
··· 82 82 rest.viewContext === PostEmbedViewContext.ThreadHighlighted 83 83 ? 'none' 84 84 : rest.viewContext === 85 - PostEmbedViewContext.FeedEmbedRecordWithMedia 86 - ? 'square' 87 - : 'constrained' 85 + PostEmbedViewContext.FeedEmbedRecordWithMedia 86 + ? 'square' 87 + : 'constrained' 88 88 } 89 89 image={image} 90 90 onPress={(containerRef, dims) => onPress(0, [containerRef], [dims])}
+2 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
··· 317 317 !focused 318 318 ? msg`Unmute video` 319 319 : playing 320 - ? msg`Pause video` 321 - : msg`Play video`, 320 + ? msg`Pause video` 321 + : msg`Play video`, 322 322 )} 323 323 accessibilityHint="" 324 324 style={[
+2 -2
src/components/PostControls/PostMenu/PostMenuItems.tsx
··· 698 698 isDetachPending 699 699 ? Loader 700 700 : quoteEmbed.isDetached 701 - ? Eye 702 - : EyeSlash 701 + ? Eye 702 + : EyeSlash 703 703 } 704 704 position="right" 705 705 />
+2 -2
src/components/Select/index.web.tsx
··· 111 111 borderColor: focused 112 112 ? t.palette.primary_500 113 113 : hovered 114 - ? t.palette.contrast_100 115 - : t.palette.contrast_25, 114 + ? t.palette.contrast_100 115 + : t.palette.contrast_25, 116 116 }, 117 117 ])}> 118 118 {children}
+6
src/components/Tooltip/const.ts
··· 1 + import {atoms as a} from '#/alf' 2 + 3 + export const BUBBLE_MAX_WIDTH = 240 4 + export const ARROW_SIZE = 12 5 + export const ARROW_HALF_SIZE = ARROW_SIZE / 2 6 + export const MIN_EDGE_SPACE = a.px_lg.paddingLeft
+411
src/components/Tooltip/index.tsx
··· 1 + import { 2 + Children, 3 + createContext, 4 + useCallback, 5 + useContext, 6 + useMemo, 7 + useRef, 8 + useState, 9 + } from 'react' 10 + import {useWindowDimensions, View} from 'react-native' 11 + import Animated, {Easing, ZoomIn} from 'react-native-reanimated' 12 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 13 + 14 + import {atoms as a, select, useTheme} from '#/alf' 15 + import {useOnGesture} from '#/components/hooks/useOnGesture' 16 + import {Portal} from '#/components/Portal' 17 + import { 18 + ARROW_HALF_SIZE, 19 + ARROW_SIZE, 20 + BUBBLE_MAX_WIDTH, 21 + MIN_EDGE_SPACE, 22 + } from '#/components/Tooltip/const' 23 + import {Text} from '#/components/Typography' 24 + 25 + /** 26 + * These are native specific values, not shared with web 27 + */ 28 + const ARROW_VISUAL_OFFSET = ARROW_SIZE / 1.25 // vibes-based, slightly off the target 29 + const BUBBLE_SHADOW_OFFSET = ARROW_SIZE / 3 // vibes-based, provide more shadow beneath tip 30 + 31 + type TooltipContextType = { 32 + position: 'top' | 'bottom' 33 + ready: boolean 34 + onVisibleChange: (visible: boolean) => void 35 + } 36 + 37 + type TargetContextType = { 38 + targetMeasurements: 39 + | { 40 + x: number 41 + y: number 42 + width: number 43 + height: number 44 + } 45 + | undefined 46 + targetRef: React.RefObject<View> 47 + } 48 + 49 + const TooltipContext = createContext<TooltipContextType>({ 50 + position: 'bottom', 51 + ready: false, 52 + onVisibleChange: () => {}, 53 + }) 54 + 55 + const TargetContext = createContext<TargetContextType>({ 56 + targetMeasurements: undefined, 57 + targetRef: {current: null}, 58 + }) 59 + 60 + export function Outer({ 61 + children, 62 + position = 'bottom', 63 + visible: requestVisible, 64 + onVisibleChange, 65 + }: { 66 + children: React.ReactNode 67 + position?: 'top' | 'bottom' 68 + visible: boolean 69 + onVisibleChange: (visible: boolean) => void 70 + }) { 71 + /** 72 + * Whether we have measured the target and are ready to show the tooltip. 73 + */ 74 + const [ready, setReady] = useState(false) 75 + /** 76 + * Lagging state to track the externally-controlled visibility of the 77 + * tooltip. 78 + */ 79 + const [prevRequestVisible, setPrevRequestVisible] = useState< 80 + boolean | undefined 81 + >() 82 + /** 83 + * Needs to reference the element this Tooltip is attached to. 84 + */ 85 + const targetRef = useRef<View>(null) 86 + const [targetMeasurements, setTargetMeasurements] = useState< 87 + | { 88 + x: number 89 + y: number 90 + width: number 91 + height: number 92 + } 93 + | undefined 94 + >(undefined) 95 + 96 + if (requestVisible && !prevRequestVisible) { 97 + setPrevRequestVisible(true) 98 + 99 + if (targetRef.current) { 100 + /* 101 + * Once opened, measure the dimensions and position of the target 102 + */ 103 + targetRef.current.measure((_x, _y, width, height, pageX, pageY) => { 104 + if (pageX !== undefined && pageY !== undefined && width && height) { 105 + setTargetMeasurements({x: pageX, y: pageY, width, height}) 106 + setReady(true) 107 + } 108 + }) 109 + } 110 + } else if (!requestVisible && prevRequestVisible) { 111 + setPrevRequestVisible(false) 112 + setTargetMeasurements(undefined) 113 + setReady(false) 114 + } 115 + 116 + const ctx = useMemo( 117 + () => ({position, ready, onVisibleChange}), 118 + [position, ready, onVisibleChange], 119 + ) 120 + const targetCtx = useMemo( 121 + () => ({targetMeasurements, targetRef}), 122 + [targetMeasurements, targetRef], 123 + ) 124 + 125 + return ( 126 + <TooltipContext.Provider value={ctx}> 127 + <TargetContext.Provider value={targetCtx}> 128 + {children} 129 + </TargetContext.Provider> 130 + </TooltipContext.Provider> 131 + ) 132 + } 133 + 134 + export function Target({children}: {children: React.ReactNode}) { 135 + const {targetRef} = useContext(TargetContext) 136 + 137 + return ( 138 + <View collapsable={false} ref={targetRef}> 139 + {children} 140 + </View> 141 + ) 142 + } 143 + 144 + export function Content({ 145 + children, 146 + label, 147 + }: { 148 + children: React.ReactNode 149 + label: string 150 + }) { 151 + const {position, ready, onVisibleChange} = useContext(TooltipContext) 152 + const {targetMeasurements} = useContext(TargetContext) 153 + const requestClose = useCallback(() => { 154 + onVisibleChange(false) 155 + }, [onVisibleChange]) 156 + 157 + if (!ready || !targetMeasurements) return null 158 + 159 + return ( 160 + <Portal> 161 + <Bubble 162 + label={label} 163 + position={position} 164 + /* 165 + * Gotta pass these in here. Inside the Bubble, we're Potal-ed outside 166 + * the context providers. 167 + */ 168 + targetMeasurements={targetMeasurements} 169 + requestClose={requestClose}> 170 + {children} 171 + </Bubble> 172 + </Portal> 173 + ) 174 + } 175 + 176 + function Bubble({ 177 + children, 178 + label, 179 + position, 180 + requestClose, 181 + targetMeasurements, 182 + }: { 183 + children: React.ReactNode 184 + label: string 185 + position: TooltipContextType['position'] 186 + requestClose: () => void 187 + targetMeasurements: Exclude< 188 + TargetContextType['targetMeasurements'], 189 + undefined 190 + > 191 + }) { 192 + const t = useTheme() 193 + const insets = useSafeAreaInsets() 194 + const dimensions = useWindowDimensions() 195 + const [bubbleMeasurements, setBubbleMeasurements] = useState< 196 + | { 197 + width: number 198 + height: number 199 + } 200 + | undefined 201 + >(undefined) 202 + const coords = useMemo(() => { 203 + if (!bubbleMeasurements) 204 + return { 205 + top: 0, 206 + bottom: 0, 207 + left: 0, 208 + right: 0, 209 + tipTop: 0, 210 + tipLeft: 0, 211 + } 212 + 213 + const {width: ww, height: wh} = dimensions 214 + const maxTop = insets.top 215 + const maxBottom = wh - insets.bottom 216 + const {width: cw, height: ch} = bubbleMeasurements 217 + const minLeft = MIN_EDGE_SPACE 218 + const maxLeft = ww - minLeft 219 + 220 + let computedPosition: 'top' | 'bottom' = position 221 + let top = targetMeasurements.y + targetMeasurements.height 222 + let left = Math.max( 223 + minLeft, 224 + targetMeasurements.x + targetMeasurements.width / 2 - cw / 2, 225 + ) 226 + const tipTranslate = ARROW_HALF_SIZE * -1 227 + let tipTop = tipTranslate 228 + 229 + if (left + cw > maxLeft) { 230 + left -= left + cw - maxLeft 231 + } 232 + 233 + let tipLeft = 234 + targetMeasurements.x - 235 + left + 236 + targetMeasurements.width / 2 - 237 + ARROW_HALF_SIZE 238 + 239 + let bottom = top + ch 240 + 241 + function positionTop() { 242 + top = top - ch - targetMeasurements.height 243 + bottom = top + ch 244 + tipTop = tipTop + ch 245 + computedPosition = 'top' 246 + } 247 + 248 + function positionBottom() { 249 + top = targetMeasurements.y + targetMeasurements.height 250 + bottom = top + ch 251 + tipTop = tipTranslate 252 + computedPosition = 'bottom' 253 + } 254 + 255 + if (position === 'top') { 256 + positionTop() 257 + if (top < maxTop) { 258 + positionBottom() 259 + } 260 + } else { 261 + if (bottom > maxBottom) { 262 + positionTop() 263 + } 264 + } 265 + 266 + if (computedPosition === 'bottom') { 267 + top += ARROW_VISUAL_OFFSET 268 + bottom += ARROW_VISUAL_OFFSET 269 + } else { 270 + top -= ARROW_VISUAL_OFFSET 271 + bottom -= ARROW_VISUAL_OFFSET 272 + } 273 + 274 + return { 275 + computedPosition, 276 + top, 277 + bottom, 278 + left, 279 + right: left + cw, 280 + tipTop, 281 + tipLeft, 282 + } 283 + }, [position, targetMeasurements, bubbleMeasurements, insets, dimensions]) 284 + 285 + const requestCloseWrapped = useCallback(() => { 286 + setBubbleMeasurements(undefined) 287 + requestClose() 288 + }, [requestClose]) 289 + 290 + useOnGesture( 291 + useCallback( 292 + e => { 293 + const {x, y} = e 294 + const isInside = 295 + x > coords.left && 296 + x < coords.right && 297 + y > coords.top && 298 + y < coords.bottom 299 + 300 + if (!isInside) { 301 + requestCloseWrapped() 302 + } 303 + }, 304 + [coords, requestCloseWrapped], 305 + ), 306 + ) 307 + 308 + return ( 309 + <View 310 + accessible 311 + role="alert" 312 + accessibilityHint="" 313 + accessibilityLabel={label} 314 + // android 315 + importantForAccessibility="yes" 316 + // ios 317 + accessibilityViewIsModal 318 + style={[ 319 + a.absolute, 320 + a.align_start, 321 + { 322 + width: BUBBLE_MAX_WIDTH, 323 + opacity: bubbleMeasurements ? 1 : 0, 324 + top: coords.top, 325 + left: coords.left, 326 + }, 327 + ]}> 328 + <Animated.View 329 + entering={ZoomIn.easing(Easing.out(Easing.exp))} 330 + style={{transformOrigin: oppposite(position)}}> 331 + <View 332 + style={[ 333 + a.absolute, 334 + a.top_0, 335 + a.z_10, 336 + t.atoms.bg, 337 + select(t.name, { 338 + light: t.atoms.bg, 339 + dark: t.atoms.bg_contrast_100, 340 + dim: t.atoms.bg_contrast_100, 341 + }), 342 + { 343 + borderTopLeftRadius: a.rounded_2xs.borderRadius, 344 + borderBottomRightRadius: a.rounded_2xs.borderRadius, 345 + width: ARROW_SIZE, 346 + height: ARROW_SIZE, 347 + transform: [{rotate: '45deg'}], 348 + top: coords.tipTop, 349 + left: coords.tipLeft, 350 + }, 351 + ]} 352 + /> 353 + <View 354 + style={[ 355 + a.px_md, 356 + a.py_sm, 357 + a.rounded_sm, 358 + select(t.name, { 359 + light: t.atoms.bg, 360 + dark: t.atoms.bg_contrast_100, 361 + dim: t.atoms.bg_contrast_100, 362 + }), 363 + t.atoms.shadow_md, 364 + { 365 + shadowOpacity: 0.2, 366 + shadowOffset: { 367 + width: 0, 368 + height: 369 + BUBBLE_SHADOW_OFFSET * 370 + (coords.computedPosition === 'bottom' ? -1 : 1), 371 + }, 372 + }, 373 + ]} 374 + onLayout={e => { 375 + setBubbleMeasurements({ 376 + width: e.nativeEvent.layout.width, 377 + height: e.nativeEvent.layout.height, 378 + }) 379 + }}> 380 + {children} 381 + </View> 382 + </Animated.View> 383 + </View> 384 + ) 385 + } 386 + 387 + function oppposite(position: 'top' | 'bottom') { 388 + switch (position) { 389 + case 'top': 390 + return 'center bottom' 391 + case 'bottom': 392 + return 'center top' 393 + default: 394 + return 'center' 395 + } 396 + } 397 + 398 + export function TextBubble({children}: {children: React.ReactNode}) { 399 + const c = Children.toArray(children) 400 + return ( 401 + <Content label={c.join(' ')}> 402 + <View style={[a.gap_xs]}> 403 + {c.map((child, i) => ( 404 + <Text key={i} style={[a.text_sm, a.leading_snug]}> 405 + {child} 406 + </Text> 407 + ))} 408 + </View> 409 + </Content> 410 + ) 411 + }
+112
src/components/Tooltip/index.web.tsx
··· 1 + import {Children, createContext, useContext, useMemo} from 'react' 2 + import {View} from 'react-native' 3 + import {Popover} from 'radix-ui' 4 + 5 + import {atoms as a, flatten, select, useTheme} from '#/alf' 6 + import {transparentifyColor} from '#/alf/util/colorGeneration' 7 + import { 8 + ARROW_SIZE, 9 + BUBBLE_MAX_WIDTH, 10 + MIN_EDGE_SPACE, 11 + } from '#/components/Tooltip/const' 12 + import {Text} from '#/components/Typography' 13 + 14 + type TooltipContextType = { 15 + position: 'top' | 'bottom' 16 + } 17 + 18 + const TooltipContext = createContext<TooltipContextType>({ 19 + position: 'bottom', 20 + }) 21 + 22 + export function Outer({ 23 + children, 24 + position = 'bottom', 25 + visible, 26 + onVisibleChange, 27 + }: { 28 + children: React.ReactNode 29 + position?: 'top' | 'bottom' 30 + visible: boolean 31 + onVisibleChange: (visible: boolean) => void 32 + }) { 33 + const ctx = useMemo(() => ({position}), [position]) 34 + return ( 35 + <Popover.Root open={visible} onOpenChange={onVisibleChange}> 36 + <TooltipContext.Provider value={ctx}>{children}</TooltipContext.Provider> 37 + </Popover.Root> 38 + ) 39 + } 40 + 41 + export function Target({children}: {children: React.ReactNode}) { 42 + return ( 43 + <Popover.Trigger asChild> 44 + <View collapsable={false}>{children}</View> 45 + </Popover.Trigger> 46 + ) 47 + } 48 + 49 + export function Content({ 50 + children, 51 + label, 52 + }: { 53 + children: React.ReactNode 54 + label: string 55 + }) { 56 + const t = useTheme() 57 + const {position} = useContext(TooltipContext) 58 + return ( 59 + <Popover.Portal> 60 + <Popover.Content 61 + className="radix-popover-content" 62 + aria-label={label} 63 + side={position} 64 + sideOffset={4} 65 + collisionPadding={MIN_EDGE_SPACE} 66 + style={flatten([ 67 + a.rounded_sm, 68 + select(t.name, { 69 + light: t.atoms.bg, 70 + dark: t.atoms.bg_contrast_100, 71 + dim: t.atoms.bg_contrast_100, 72 + }), 73 + { 74 + minWidth: 'max-content', 75 + boxShadow: select(t.name, { 76 + light: `0 0 24px ${transparentifyColor(t.palette.black, 0.2)}`, 77 + dark: `0 0 24px ${transparentifyColor(t.palette.black, 0.2)}`, 78 + dim: `0 0 24px ${transparentifyColor(t.palette.black, 0.2)}`, 79 + }), 80 + }, 81 + ])}> 82 + <Popover.Arrow 83 + width={ARROW_SIZE} 84 + height={ARROW_SIZE / 2} 85 + fill={select(t.name, { 86 + light: t.atoms.bg.backgroundColor, 87 + dark: t.atoms.bg_contrast_100.backgroundColor, 88 + dim: t.atoms.bg_contrast_100.backgroundColor, 89 + })} 90 + /> 91 + <View style={[a.px_md, a.py_sm, {maxWidth: BUBBLE_MAX_WIDTH}]}> 92 + {children} 93 + </View> 94 + </Popover.Content> 95 + </Popover.Portal> 96 + ) 97 + } 98 + 99 + export function TextBubble({children}: {children: React.ReactNode}) { 100 + const c = Children.toArray(children) 101 + return ( 102 + <Content label={c.join(' ')}> 103 + <View style={[a.gap_xs]}> 104 + {c.map((child, i) => ( 105 + <Text key={i} style={[a.text_sm, a.leading_snug]}> 106 + {child} 107 + </Text> 108 + ))} 109 + </View> 110 + </Content> 111 + ) 112 + }
+12 -6
src/components/WhoCanReply.tsx
··· 1 1 import React from 'react' 2 - import {Keyboard, Platform, StyleProp, View, ViewStyle} from 'react-native' 3 2 import { 4 - AppBskyFeedDefs, 3 + Keyboard, 4 + Platform, 5 + type StyleProp, 6 + View, 7 + type ViewStyle, 8 + } from 'react-native' 9 + import { 10 + type AppBskyFeedDefs, 5 11 AppBskyFeedPost, 6 - AppBskyGraphDefs, 12 + type AppBskyGraphDefs, 7 13 AtUri, 8 14 } from '@atproto/api' 9 15 import {msg, Trans} from '@lingui/macro' ··· 13 19 import {makeListLink, makeProfileLink} from '#/lib/routes/links' 14 20 import {isNative} from '#/platform/detection' 15 21 import { 16 - ThreadgateAllowUISetting, 22 + type ThreadgateAllowUISetting, 17 23 threadgateViewToAllowUISetting, 18 24 } from '#/state/queries/threadgate' 19 25 import {atoms as a, useTheme} from '#/alf' ··· 70 76 const description = anyoneCanReply 71 77 ? _(msg`Everybody can reply`) 72 78 : noOneCanReply 73 - ? _(msg`Replies disabled`) 74 - : _(msg`Some people can reply`) 79 + ? _(msg`Replies disabled`) 80 + : _(msg`Some people can reply`) 75 81 76 82 const onPressOpen = () => { 77 83 if (isNative && Keyboard.isVisible()) {
+2 -2
src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx
··· 185 185 state.emailStatus === 'pending' 186 186 ? Loader 187 187 : state.emailStatus === 'success' 188 - ? Check 189 - : Envelope 188 + ? Check 189 + : Envelope 190 190 } 191 191 /> 192 192 </Button>
+2 -2
src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx
··· 116 116 state.status === 'pending' 117 117 ? Loader 118 118 : state.status === 'success' 119 - ? Check 120 - : ShieldIcon 119 + ? Check 120 + : ShieldIcon 121 121 } 122 122 /> 123 123 </Button>
+2 -2
src/components/dialogs/SearchablePeopleList.tsx
··· 390 390 !enabled 391 391 ? {opacity: 0.5} 392 392 : pressed || focused || hovered 393 - ? t.atoms.bg_contrast_25 394 - : t.atoms.bg, 393 + ? t.atoms.bg_contrast_25 394 + : t.atoms.bg, 395 395 ]}> 396 396 <ProfileCard.Header> 397 397 <ProfileCard.Avatar
+2 -2
src/components/dms/EmojiReactionPicker.tsx
··· 98 98 : t.palette.primary_500, 99 99 } 100 100 : alreadyReacted 101 - ? {backgroundColor: t.palette.primary_200} 102 - : bgColor, 101 + ? {backgroundColor: t.palette.primary_200} 102 + : bgColor, 103 103 {height: 40, width: 40}, 104 104 a.justify_center, 105 105 a.align_center,
+1 -2
src/components/dms/MessageContextMenu.tsx
··· 128 128 label={_(msg`Message options`)} 129 129 contentLabel={_( 130 130 msg`Message from @${ 131 - sender?.handle ?? // should always be defined 132 - 'unknown' 131 + sender?.handle ?? 'unknown' // should always be defined 133 132 }: ${message.text}`, 134 133 )}> 135 134 {children}
+24
src/components/hooks/useOnGesture/index.ts
··· 1 + import {useEffect} from 'react' 2 + 3 + import { 4 + type GlobalGestureEvents, 5 + useGlobalGestureEvents, 6 + } from '#/state/global-gesture-events' 7 + 8 + /** 9 + * Listen for global gesture events. Callback should be wrapped with 10 + * `useCallback` or otherwise memoized to avoid unnecessary re-renders. 11 + */ 12 + export function useOnGesture( 13 + onGestureCallback: (e: GlobalGestureEvents['begin']) => void, 14 + ) { 15 + const ctx = useGlobalGestureEvents() 16 + useEffect(() => { 17 + ctx.register() 18 + ctx.events.on('begin', onGestureCallback) 19 + return () => { 20 + ctx.unregister() 21 + ctx.events.off('begin', onGestureCallback) 22 + } 23 + }, [ctx, onGestureCallback]) 24 + }
+1
src/components/hooks/useOnGesture/index.web.ts
··· 1 + export function useOnGesture() {}
+23 -24
src/components/icons/VerifiedCheck.tsx
··· 3 3 4 4 import {type Props, useCommonSVGProps} from '#/components/icons/common' 5 5 6 - export const VerifiedCheck = React.forwardRef<Svg, Props>(function LogoImpl( 7 - props, 8 - ref, 9 - ) { 10 - const {fill, size, style, ...rest} = useCommonSVGProps(props) 6 + export const VerifiedCheck = React.forwardRef<Svg, Props>( 7 + function LogoImpl(props, ref) { 8 + const {fill, size, style, ...rest} = useCommonSVGProps(props) 11 9 12 - return ( 13 - <Svg 14 - fill="none" 15 - {...rest} 16 - ref={ref} 17 - viewBox="0 0 24 24" 18 - width={size} 19 - height={size} 20 - style={[style]}> 21 - <Circle cx="12" cy="12" r="11.5" fill={fill} /> 22 - <Path 23 - fill="#fff" 24 - fillRule="evenodd" 25 - clipRule="evenodd" 26 - d="M17.659 8.175a1.361 1.361 0 0 1 0 1.925l-6.224 6.223a1.361 1.361 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.361 1.361 0 0 1 1.925 0Z" 27 - /> 28 - </Svg> 29 - ) 30 - }) 10 + return ( 11 + <Svg 12 + fill="none" 13 + {...rest} 14 + ref={ref} 15 + viewBox="0 0 24 24" 16 + width={size} 17 + height={size} 18 + style={[style]}> 19 + <Circle cx="12" cy="12" r="11.5" fill={fill} /> 20 + <Path 21 + fill="#fff" 22 + fillRule="evenodd" 23 + clipRule="evenodd" 24 + d="M17.659 8.175a1.361 1.361 0 0 1 0 1.925l-6.224 6.223a1.361 1.361 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.361 1.361 0 0 1 1.925 0Z" 25 + /> 26 + </Svg> 27 + ) 28 + }, 29 + )
+28 -29
src/components/icons/VerifierCheck.tsx
··· 3 3 4 4 import {type Props, useCommonSVGProps} from '#/components/icons/common' 5 5 6 - export const VerifierCheck = React.forwardRef<Svg, Props>(function LogoImpl( 7 - props, 8 - ref, 9 - ) { 10 - const {fill, size, style, ...rest} = useCommonSVGProps(props) 6 + export const VerifierCheck = React.forwardRef<Svg, Props>( 7 + function LogoImpl(props, ref) { 8 + const {fill, size, style, ...rest} = useCommonSVGProps(props) 11 9 12 - return ( 13 - <Svg 14 - fill="none" 15 - {...rest} 16 - ref={ref} 17 - viewBox="0 0 24 24" 18 - width={size} 19 - height={size} 20 - style={[style]}> 21 - <Path 22 - fill={fill} 23 - fillRule="evenodd" 24 - clipRule="evenodd" 25 - d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.154 4.154 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.154 4.154 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.153 4.153 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.154 4.154 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.154 4.154 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.154 4.154 0 0 0 .776-3.404A4.154 4.154 0 0 1 5.646 3.13a4.154 4.154 0 0 0 3.146-1.515Z" 26 - /> 27 - <Path 28 - fill="#fff" 29 - fillRule="evenodd" 30 - clipRule="evenodd" 31 - d="M17.861 8.26a1.438 1.438 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z" 32 - /> 33 - </Svg> 34 - ) 35 - }) 10 + return ( 11 + <Svg 12 + fill="none" 13 + {...rest} 14 + ref={ref} 15 + viewBox="0 0 24 24" 16 + width={size} 17 + height={size} 18 + style={[style]}> 19 + <Path 20 + fill={fill} 21 + fillRule="evenodd" 22 + clipRule="evenodd" 23 + d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.154 4.154 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.154 4.154 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.153 4.153 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.154 4.154 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.154 4.154 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.154 4.154 0 0 0 .776-3.404A4.154 4.154 0 0 1 5.646 3.13a4.154 4.154 0 0 0 3.146-1.515Z" 24 + /> 25 + <Path 26 + fill="#fff" 27 + fillRule="evenodd" 28 + clipRule="evenodd" 29 + d="M17.861 8.26a1.438 1.438 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z" 30 + /> 31 + </Svg> 32 + ) 33 + }, 34 + )
+4 -4
src/components/moderation/ContentHider.tsx
··· 1 1 import React from 'react' 2 - import {StyleProp, View, ViewStyle} from 'react-native' 3 - import {ModerationUI} from '@atproto/api' 2 + import {type StyleProp, View, type ViewStyle} from 'react-native' 3 + import {type ModerationUI} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 148 148 modui.noOverride 149 149 ? _(msg`Learn more about the moderation applied to this content`) 150 150 : override 151 - ? _(msg`Hides the content`) 152 - : _(msg`Shows the content`) 151 + ? _(msg`Hides the content`) 152 + : _(msg`Shows the content`) 153 153 }> 154 154 {state => ( 155 155 <View
+6 -6
src/components/moderation/ReportDialog/index.tsx
··· 512 512 backgroundColor: active 513 513 ? t.palette.primary_500 514 514 : completed 515 - ? t.palette.primary_100 516 - : t.atoms.bg_contrast_25.backgroundColor, 515 + ? t.palette.primary_100 516 + : t.atoms.bg_contrast_25.backgroundColor, 517 517 borderColor: active 518 518 ? t.palette.primary_500 519 519 : completed 520 - ? t.palette.primary_400 521 - : t.atoms.border_contrast_low.borderColor, 520 + ? t.palette.primary_400 521 + : t.atoms.border_contrast_low.borderColor, 522 522 }, 523 523 ]}> 524 524 {completed ? ( ··· 533 533 color: active 534 534 ? 'white' 535 535 : completed 536 - ? t.palette.primary_700 537 - : t.atoms.text_contrast_medium.color, 536 + ? t.palette.primary_700 537 + : t.atoms.text_contrast_medium.color, 538 538 fontVariant: ['tabular-nums'], 539 539 width: 24, 540 540 height: 24,
+2 -2
src/components/verification/VerificationCheckButton.tsx
··· 130 130 verifiedByHidden 131 131 ? t.atoms.bg_contrast_100.backgroundColor 132 132 : state.profile.isVerified 133 - ? t.palette.primary_500 134 - : t.atoms.bg_contrast_100.backgroundColor 133 + ? t.palette.primary_500 134 + : t.atoms.bg_contrast_100.backgroundColor 135 135 } 136 136 verifier={state.profile.role === 'verifier'} 137 137 />
+7 -7
src/components/verification/VerificationsDialog.tsx
··· 64 64 ? _(msg`You are verified`) 65 65 : _(msg`Your verifications`) 66 66 : state.profile.isVerified 67 - ? _(msg`${userName} is verified`) 68 - : _( 69 - msg({ 70 - message: `${userName}'s verifications`, 71 - comment: `Possessive, meaning "the verifications of {userName}"`, 72 - }), 73 - ) 67 + ? _(msg`${userName} is verified`) 68 + : _( 69 + msg({ 70 + message: `${userName}'s verifications`, 71 + comment: `Possessive, meaning "the verifications of {userName}"`, 72 + }), 73 + ) 74 74 75 75 return ( 76 76 <Dialog.ScrollableInner
+9 -9
src/lib/moderation.ts
··· 1 1 import React from 'react' 2 2 import { 3 - AppBskyLabelerDefs, 3 + type AppBskyLabelerDefs, 4 4 BskyAgent, 5 - ComAtprotoLabelDefs, 6 - InterpretedLabelValueDefinition, 5 + type ComAtprotoLabelDefs, 6 + type InterpretedLabelValueDefinition, 7 7 LABELS, 8 - ModerationCause, 9 - ModerationOpts, 10 - ModerationUI, 8 + type ModerationCause, 9 + type ModerationOpts, 10 + type ModerationUI, 11 11 } from '@atproto/api' 12 12 13 13 import {sanitizeDisplayName} from '#/lib/strings/display-names' 14 14 import {sanitizeHandle} from '#/lib/strings/handles' 15 - import {AppModerationCause} from '#/components/Pills' 15 + import {type AppModerationCause} from '#/components/Pills' 16 16 17 17 export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] 18 18 export const OTHER_SELF_LABELS = ['graphic-media'] ··· 29 29 cause.source.type === 'labeler' 30 30 ? cause.source.did 31 31 : cause.source.type === 'list' 32 - ? cause.source.list.uri 33 - : 'user' 32 + ? cause.source.list.uri 33 + : 'user' 34 34 if (cause.type === 'label') { 35 35 return `label:${cause.label.val}:${source}` 36 36 }
+6 -6
src/lib/moderation/useModerationCauseDescription.ts
··· 1 1 import React from 'react' 2 2 import { 3 3 BSKY_LABELER_DID, 4 - ModerationCause, 5 - ModerationCauseSource, 4 + type ModerationCause, 5 + type ModerationCauseSource, 6 6 } from '@atproto/api' 7 7 import {msg} from '@lingui/macro' 8 8 import {useLingui} from '@lingui/react' ··· 12 12 import {useSession} from '#/state/session' 13 13 import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' 14 14 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 15 - import {Props as SVGIconProps} from '#/components/icons/common' 15 + import {type Props as SVGIconProps} from '#/components/icons/common' 16 16 import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' 17 17 import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' 18 - import {AppModerationCause} from '#/components/Pills' 18 + import {type AppModerationCause} from '#/components/Pills' 19 19 import {useGlobalLabelStrings} from './useGlobalLabelStrings' 20 20 import {getDefinition, getLabelStrings} from './useLabelInfo' 21 21 ··· 153 153 def.identifier === '!no-unauthenticated' 154 154 ? EyeSlash 155 155 : def.severity === 'alert' 156 - ? Warning 157 - : CircleInfo, 156 + ? Warning 157 + : CircleInfo, 158 158 name: strings.name, 159 159 description: strings.description, 160 160 source,
+55 -55
src/locale/locales/en/messages.po
··· 456 456 msgid "<0>{0}</0> is included in your starter pack" 457 457 msgstr "" 458 458 459 - #: src/components/WhoCanReply.tsx:296 459 + #: src/components/WhoCanReply.tsx:302 460 460 msgid "<0>{0}</0> members" 461 461 msgstr "" 462 462 ··· 866 866 msgid "An error occurred while compressing the video." 867 867 msgstr "" 868 868 869 - #: src/state/queries/explore-feed-previews.tsx:184 869 + #: src/state/queries/explore-feed-previews.tsx:173 870 870 msgid "An error occurred while fetching the feed." 871 871 msgstr "" 872 872 ··· 939 939 msgid "an unknown labeler" 940 940 msgstr "" 941 941 942 - #: src/components/WhoCanReply.tsx:317 942 + #: src/components/WhoCanReply.tsx:323 943 943 msgid "and" 944 944 msgstr "" 945 945 ··· 1066 1066 msgid "Are you sure you want to delete the app password \"{0}\"?" 1067 1067 msgstr "" 1068 1068 1069 - #: src/components/dms/MessageContextMenu.tsx:189 1069 + #: src/components/dms/MessageContextMenu.tsx:188 1070 1070 msgid "Are you sure you want to delete this message? The message will be deleted for you, but not for the other participant." 1071 1071 msgstr "" 1072 1072 ··· 1128 1128 msgid "At least 3 characters" 1129 1129 msgstr "" 1130 1130 1131 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:40 1131 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:48 1132 1132 msgctxt "Name of app icon variant" 1133 1133 msgid "Aurora" 1134 1134 msgstr "" ··· 1302 1302 msgid "Bluesky cannot confirm the authenticity of the claimed date." 1303 1303 msgstr "" 1304 1304 1305 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:129 1305 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:165 1306 1306 msgctxt "Name of app icon variant" 1307 1307 msgid "Bluesky Classic™" 1308 1308 msgstr "" ··· 2096 2096 msgid "Copy link to starter pack" 2097 2097 msgstr "" 2098 2098 2099 - #: src/components/dms/MessageContextMenu.tsx:150 2100 - #: src/components/dms/MessageContextMenu.tsx:153 2099 + #: src/components/dms/MessageContextMenu.tsx:149 2100 + #: src/components/dms/MessageContextMenu.tsx:152 2101 2101 msgid "Copy message text" 2102 2102 msgstr "" 2103 2103 ··· 2145 2145 msgid "Could not process your video" 2146 2146 msgstr "" 2147 2147 2148 - #: src/state/queries/notifications/settings.ts:50 2148 + #: src/state/queries/notifications/settings.ts:49 2149 2149 msgid "Could not update notification settings" 2150 2150 msgstr "" 2151 2151 ··· 2247 2247 msgid "Dark" 2248 2248 msgstr "" 2249 2249 2250 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:24 2250 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:28 2251 2251 msgctxt "Name of app icon variant" 2252 2252 msgid "Dark" 2253 2253 msgstr "" ··· 2286 2286 msgid "Default icons" 2287 2287 msgstr "" 2288 2288 2289 - #: src/components/dms/MessageContextMenu.tsx:191 2289 + #: src/components/dms/MessageContextMenu.tsx:190 2290 2290 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:701 2291 2291 #: src/screens/Messages/components/ChatStatusInfo.tsx:55 2292 2292 #: src/screens/Settings/AppPasswords.tsx:212 ··· 2334 2334 msgid "Delete Conversation" 2335 2335 msgstr "" 2336 2336 2337 - #: src/components/dms/MessageContextMenu.tsx:164 2337 + #: src/components/dms/MessageContextMenu.tsx:163 2338 2338 msgid "Delete for me" 2339 2339 msgstr "" 2340 2340 ··· 2342 2342 msgid "Delete list" 2343 2343 msgstr "" 2344 2344 2345 - #: src/components/dms/MessageContextMenu.tsx:187 2345 + #: src/components/dms/MessageContextMenu.tsx:186 2346 2346 msgid "Delete message" 2347 2347 msgstr "" 2348 2348 2349 - #: src/components/dms/MessageContextMenu.tsx:162 2349 + #: src/components/dms/MessageContextMenu.tsx:161 2350 2350 msgid "Delete message for me" 2351 2351 msgstr "" 2352 2352 ··· 2433 2433 msgid "Developer options" 2434 2434 msgstr "" 2435 2435 2436 - #: src/components/WhoCanReply.tsx:179 2436 + #: src/components/WhoCanReply.tsx:185 2437 2437 msgid "Dialog: adjust who can interact with this post" 2438 2438 msgstr "" 2439 2439 ··· 2759 2759 msgid "Edit User List" 2760 2760 msgstr "" 2761 2761 2762 - #: src/components/WhoCanReply.tsx:91 2762 + #: src/components/WhoCanReply.tsx:97 2763 2763 msgid "Edit who can reply" 2764 2764 msgstr "" 2765 2765 ··· 2984 2984 msgid "Everybody" 2985 2985 msgstr "" 2986 2986 2987 - #: src/components/WhoCanReply.tsx:71 2987 + #: src/components/WhoCanReply.tsx:77 2988 2988 msgid "Everybody can reply" 2989 2989 msgstr "" 2990 2990 2991 - #: src/components/WhoCanReply.tsx:222 2991 + #: src/components/WhoCanReply.tsx:228 2992 2992 msgid "Everybody can reply to this post." 2993 2993 msgstr "" 2994 2994 ··· 3409 3409 msgid "Fitness" 3410 3410 msgstr "" 3411 3411 3412 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:117 3412 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:149 3413 3413 msgctxt "Name of app icon variant" 3414 3414 msgid "Flat Black" 3415 3415 msgstr "" 3416 3416 3417 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:93 3417 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:117 3418 3418 msgctxt "Name of app icon variant" 3419 3419 msgid "Flat Blue" 3420 3420 msgstr "" 3421 3421 3422 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:105 3422 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:133 3423 3423 msgctxt "Name of app icon variant" 3424 3424 msgid "Flat White" 3425 3425 msgstr "" ··· 3920 3920 msgid "Hides the content" 3921 3921 msgstr "" 3922 3922 3923 - #: src/view/com/posts/PostFeedErrorMessage.tsx:117 3923 + #: src/view/com/posts/PostFeedErrorMessage.tsx:121 3924 3924 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." 3925 3925 msgstr "" 3926 3926 3927 - #: src/view/com/posts/PostFeedErrorMessage.tsx:105 3927 + #: src/view/com/posts/PostFeedErrorMessage.tsx:109 3928 3928 msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." 3929 3929 msgstr "" 3930 3930 3931 - #: src/view/com/posts/PostFeedErrorMessage.tsx:111 3931 + #: src/view/com/posts/PostFeedErrorMessage.tsx:115 3932 3932 msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." 3933 3933 msgstr "" 3934 3934 3935 - #: src/view/com/posts/PostFeedErrorMessage.tsx:108 3935 + #: src/view/com/posts/PostFeedErrorMessage.tsx:112 3936 3936 msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." 3937 3937 msgstr "" 3938 3938 3939 - #: src/view/com/posts/PostFeedErrorMessage.tsx:102 3939 + #: src/view/com/posts/PostFeedErrorMessage.tsx:106 3940 3940 msgid "Hmm, we're having trouble finding this feed. It may have been deleted." 3941 3941 msgstr "" 3942 3942 ··· 4680 4680 msgid "Mention notifications" 4681 4681 msgstr "" 4682 4682 4683 - #: src/components/WhoCanReply.tsx:263 4683 + #: src/components/WhoCanReply.tsx:269 4684 4684 msgid "mentioned users" 4685 4685 msgstr "" 4686 4686 ··· 4716 4716 msgid "Message from @{0}: {1}" 4717 4717 msgstr "" 4718 4718 4719 - #: src/view/com/posts/PostFeedErrorMessage.tsx:201 4719 + #: src/view/com/posts/PostFeedErrorMessage.tsx:205 4720 4720 msgid "Message from server: {0}" 4721 4721 msgstr "" 4722 4722 ··· 4737 4737 msgid "Messages" 4738 4738 msgstr "" 4739 4739 4740 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:81 4740 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:101 4741 4741 msgctxt "Name of app icon variant" 4742 4742 msgid "Midnight" 4743 4743 msgstr "" ··· 5193 5193 msgid "No one" 5194 5194 msgstr "" 5195 5195 5196 - #: src/components/WhoCanReply.tsx:246 5196 + #: src/components/WhoCanReply.tsx:252 5197 5197 msgid "No one but the author can quote this post." 5198 5198 msgstr "" 5199 5199 ··· 5419 5419 msgid "Only .jpg and .png files are supported" 5420 5420 msgstr "" 5421 5421 5422 - #: src/components/WhoCanReply.tsx:226 5422 + #: src/components/WhoCanReply.tsx:232 5423 5423 msgid "Only {0} can reply." 5424 5424 msgstr "" 5425 5425 ··· 6057 6057 msgid "Posts can be muted based on their text, their tags, or both. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." 6058 6058 msgstr "" 6059 6059 6060 - #: src/view/com/posts/PostFeedErrorMessage.tsx:68 6060 + #: src/view/com/posts/PostFeedErrorMessage.tsx:72 6061 6061 msgid "Posts hidden" 6062 6062 msgstr "" 6063 6063 ··· 6361 6361 #: src/screens/Settings/Settings.tsx:556 6362 6362 #: src/view/com/feeds/FeedSourceCard.tsx:322 6363 6363 #: src/view/com/modals/UserAddRemoveLists.tsx:235 6364 - #: src/view/com/posts/PostFeedErrorMessage.tsx:213 6364 + #: src/view/com/posts/PostFeedErrorMessage.tsx:217 6365 6365 msgid "Remove" 6366 6366 msgstr "" 6367 6367 ··· 6398 6398 6399 6399 #: src/view/com/posts/FeedShutdownMsg.tsx:116 6400 6400 #: src/view/com/posts/FeedShutdownMsg.tsx:120 6401 - #: src/view/com/posts/PostFeedErrorMessage.tsx:169 6401 + #: src/view/com/posts/PostFeedErrorMessage.tsx:173 6402 6402 msgid "Remove feed" 6403 6403 msgstr "" 6404 6404 6405 - #: src/view/com/posts/PostFeedErrorMessage.tsx:210 6405 + #: src/view/com/posts/PostFeedErrorMessage.tsx:214 6406 6406 msgid "Remove feed?" 6407 6407 msgstr "" 6408 6408 ··· 6455 6455 msgid "Remove subtitle file" 6456 6456 msgstr "" 6457 6457 6458 - #: src/view/com/posts/PostFeedErrorMessage.tsx:211 6458 + #: src/view/com/posts/PostFeedErrorMessage.tsx:215 6459 6459 msgid "Remove this feed from your saved feeds" 6460 6460 msgstr "" 6461 6461 ··· 6517 6517 msgid "Replies" 6518 6518 msgstr "" 6519 6519 6520 - #: src/components/WhoCanReply.tsx:73 6520 + #: src/components/WhoCanReply.tsx:79 6521 6521 msgid "Replies disabled" 6522 6522 msgstr "" 6523 6523 6524 - #: src/components/WhoCanReply.tsx:224 6524 + #: src/components/WhoCanReply.tsx:230 6525 6525 msgid "Replies to this post are disabled." 6526 6526 msgstr "" 6527 6527 ··· 6593 6593 msgid "Reply was successfully hidden" 6594 6594 msgstr "" 6595 6595 6596 - #: src/components/dms/MessageContextMenu.tsx:172 6596 + #: src/components/dms/MessageContextMenu.tsx:171 6597 6597 #: src/components/dms/MessagesListBlockedFooter.tsx:85 6598 6598 #: src/components/dms/MessagesListBlockedFooter.tsx:92 6599 6599 msgid "Report" ··· 6627 6627 msgid "Report list" 6628 6628 msgstr "" 6629 6629 6630 - #: src/components/dms/MessageContextMenu.tsx:170 6630 + #: src/components/dms/MessageContextMenu.tsx:169 6631 6631 msgid "Report message" 6632 6632 msgstr "" 6633 6633 ··· 7646 7646 msgid "Some other feeds you might like" 7647 7647 msgstr "" 7648 7648 7649 - #: src/components/WhoCanReply.tsx:74 7649 + #: src/components/WhoCanReply.tsx:80 7650 7650 msgid "Some people can reply" 7651 7651 msgstr "" 7652 7652 ··· 7871 7871 msgid "Suggestive" 7872 7872 msgstr "" 7873 7873 7874 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:60 7874 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:72 7875 7875 msgctxt "Name of app icon variant" 7876 7876 msgid "Sunrise" 7877 7877 msgstr "" 7878 7878 7879 - #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:70 7879 + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:86 7880 7880 msgctxt "Name of app icon variant" 7881 7881 msgid "Sunset" 7882 7882 msgstr "" ··· 8165 8165 msgstr "" 8166 8166 8167 8167 #: src/screens/Search/Explore.tsx:990 8168 - #: src/view/com/posts/PostFeed.tsx:657 8168 + #: src/view/com/posts/PostFeed.tsx:656 8169 8169 msgid "There was an issue fetching posts. Tap here to try again." 8170 8170 msgstr "" 8171 8171 ··· 8186 8186 msgid "There was an issue fetching your service info" 8187 8187 msgstr "" 8188 8188 8189 - #: src/view/com/posts/PostFeedErrorMessage.tsx:145 8189 + #: src/view/com/posts/PostFeedErrorMessage.tsx:149 8190 8190 msgid "There was an issue removing this feed. Please check your internet connection and try again." 8191 8191 msgstr "" 8192 8192 ··· 8295 8295 msgid "This content is not available because one of the users involved has blocked the other." 8296 8296 msgstr "" 8297 8297 8298 - #: src/view/com/posts/PostFeedErrorMessage.tsx:114 8298 + #: src/view/com/posts/PostFeedErrorMessage.tsx:118 8299 8299 msgid "This content is not viewable without a Bluesky account." 8300 8300 msgstr "" 8301 8301 ··· 8319 8319 msgid "This feature is not available while using an App Password. Please sign in with your main password." 8320 8320 msgstr "" 8321 8321 8322 - #: src/view/com/posts/PostFeedErrorMessage.tsx:120 8322 + #: src/view/com/posts/PostFeedErrorMessage.tsx:124 8323 8323 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." 8324 8324 msgstr "" 8325 8325 ··· 8387 8387 msgid "This post claims to have been created on <0>{0}</0>, but was first seen by Bluesky on <1>{1}</1>." 8388 8388 msgstr "" 8389 8389 8390 - #: src/components/WhoCanReply.tsx:217 8390 + #: src/components/WhoCanReply.tsx:223 8391 8391 msgid "This post has an unknown type of threadgate on it. Your app may be out of date." 8392 8392 msgstr "" 8393 8393 ··· 8558 8558 msgid "Topic" 8559 8559 msgstr "" 8560 8560 8561 - #: src/components/dms/MessageContextMenu.tsx:143 8562 - #: src/components/dms/MessageContextMenu.tsx:145 8561 + #: src/components/dms/MessageContextMenu.tsx:142 8562 + #: src/components/dms/MessageContextMenu.tsx:144 8563 8563 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:444 8564 8564 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:446 8565 8565 #: src/screens/PostThread/components/ThreadItemAnchor.tsx:567 ··· 8984 8984 msgid "Username or email address" 8985 8985 msgstr "" 8986 8986 8987 - #: src/components/WhoCanReply.tsx:280 8987 + #: src/components/WhoCanReply.tsx:286 8988 8988 msgid "users followed by <0>@{0}</0>" 8989 8989 msgstr "" 8990 8990 8991 - #: src/components/WhoCanReply.tsx:267 8991 + #: src/components/WhoCanReply.tsx:273 8992 8992 msgid "users following <0>@{0}</0>" 8993 8993 msgstr "" 8994 8994 ··· 9188 9188 #: src/components/ProfileHoverCard/index.web.tsx:464 9189 9189 #: src/components/ProfileHoverCard/index.web.tsx:484 9190 9190 #: src/components/ProfileHoverCard/index.web.tsx:511 9191 - #: src/view/com/posts/PostFeedErrorMessage.tsx:175 9191 + #: src/view/com/posts/PostFeedErrorMessage.tsx:179 9192 9192 #: src/view/com/util/PostMeta.tsx:91 9193 9193 #: src/view/com/util/PostMeta.tsx:126 9194 9194 msgid "View profile" ··· 9416 9416 msgid "Which languages would you like to see in your algorithmic feeds?" 9417 9417 msgstr "" 9418 9418 9419 - #: src/components/WhoCanReply.tsx:183 9419 + #: src/components/WhoCanReply.tsx:189 9420 9420 msgid "Who can interact with this post?" 9421 9421 msgstr "" 9422 9422 9423 - #: src/components/WhoCanReply.tsx:91 9423 + #: src/components/WhoCanReply.tsx:97 9424 9424 msgid "Who can reply" 9425 9425 msgstr "" 9426 9426
+6 -6
src/platform/polyfills.ts
··· 41 41 r1 === 64 42 42 ? String.fromCharCode((bitmap >> 16) & 255) 43 43 : r2 === 64 44 - ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255) 45 - : String.fromCharCode( 46 - (bitmap >> 16) & 255, 47 - (bitmap >> 8) & 255, 48 - bitmap & 255, 49 - ) 44 + ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255) 45 + : String.fromCharCode( 46 + (bitmap >> 16) & 255, 47 + (bitmap >> 8) & 255, 48 + bitmap & 255, 49 + ) 50 50 } 51 51 return result 52 52 }
+2 -2
src/screens/Login/index.tsx
··· 49 49 requestedAccount 50 50 ? Forms.Login 51 51 : accounts.length 52 - ? Forms.ChooseAccount 53 - : Forms.Login, 52 + ? Forms.ChooseAccount 53 + : Forms.Login, 54 54 ) 55 55 56 56 const {
+1 -1
src/screens/Messages/ChatList.tsx
··· 155 155 profiles: inboxPreviewConvos.slice(0, 3), 156 156 }, 157 157 ...conversations.map( 158 - convo => ({type: 'CONVERSATION', conversation: convo} as const), 158 + convo => ({type: 'CONVERSATION', conversation: convo}) as const, 159 159 ), 160 160 ] satisfies ListItem[] 161 161 }
+2 -2
src/screens/Onboarding/StepFinished.tsx
··· 176 176 avatarResult: profileStepResults.isCreatedAvatar 177 177 ? 'created' 178 178 : profileStepResults.image 179 - ? 'uploaded' 180 - : 'default', 179 + ? 'uploaded' 180 + : 'default', 181 181 }) 182 182 })(), 183 183 requestNotificationsPermission('AfterOnboarding'),
+2 -2
src/screens/Profile/Header/ProfileHeaderLabeler.tsx
··· 214 214 ? t.palette.contrast_50 215 215 : t.palette.contrast_25 216 216 : state.hovered || state.pressed 217 - ? tokens.color.temp_purple_dark 218 - : tokens.color.temp_purple, 217 + ? tokens.color.temp_purple_dark 218 + : tokens.color.temp_purple, 219 219 }, 220 220 ]}> 221 221 <Text
+2 -2
src/screens/Search/Explore.tsx
··· 342 342 ]) 343 343 344 344 const topBorder = useMemo( 345 - () => ({type: 'topBorder', key: 'top-border'} as const), 345 + () => ({type: 'topBorder', key: 'top-border'}) as const, 346 346 [], 347 347 ) 348 348 const trendingTopicsModule = useMemo( 349 - () => ({type: 'trendingTopics', key: 'trending-topics'} as const), 349 + () => ({type: 'trendingTopics', key: 'trending-topics'}) as const, 350 350 [], 351 351 ) 352 352 const suggestedFollowsModule = useMemo(() => {
+12 -4
src/screens/Settings/AppIconSettings/useAppIconSets.ts
··· 13 13 id: 'default_light', 14 14 name: _(msg({context: 'Name of app icon variant', message: 'Light'})), 15 15 iosImage: () => { 16 - return require(`../../../../assets/app-icons/ios_icon_default_light.png`) 16 + return require( 17 + `../../../../assets/app-icons/ios_icon_default_light.png`, 18 + ) 17 19 }, 18 20 androidImage: () => { 19 - return require(`../../../../assets/app-icons/android_icon_default_light.png`) 21 + return require( 22 + `../../../../assets/app-icons/android_icon_default_light.png`, 23 + ) 20 24 }, 21 25 }, 22 26 { 23 27 id: 'default_dark', 24 28 name: _(msg({context: 'Name of app icon variant', message: 'Dark'})), 25 29 iosImage: () => { 26 - return require(`../../../../assets/app-icons/ios_icon_default_dark.png`) 30 + return require( 31 + `../../../../assets/app-icons/ios_icon_default_dark.png`, 32 + ) 27 33 }, 28 34 androidImage: () => { 29 - return require(`../../../../assets/app-icons/android_icon_default_dark.png`) 35 + return require( 36 + `../../../../assets/app-icons/android_icon_default_dark.png`, 37 + ) 30 38 }, 31 39 }, 32 40 ] satisfies AppIconSet[]
+2 -2
src/screens/Settings/components/ChangeHandleDialog.tsx
··· 525 525 isVerified 526 526 ? _(msg`Update to ${domain}`) 527 527 : dnsPanel 528 - ? _(msg`Verify DNS Record`) 529 - : _(msg`Verify Text File`) 528 + ? _(msg`Verify DNS Record`) 529 + : _(msg`Verify Text File`) 530 530 } 531 531 variant="solid" 532 532 size="large"
+2 -2
src/state/cache/profile-shadow.ts
··· 146 146 'status' in shadow 147 147 ? shadow.status 148 148 : 'status' in profile 149 - ? profile.status 150 - : undefined, 149 + ? profile.status 150 + : undefined, 151 151 }) 152 152 } 153 153
+83
src/state/global-gesture-events/index.tsx
··· 1 + import {createContext, useContext, useMemo, useRef, useState} from 'react' 2 + import {View} from 'react-native' 3 + import { 4 + Gesture, 5 + GestureDetector, 6 + type GestureStateChangeEvent, 7 + type GestureUpdateEvent, 8 + type PanGestureHandlerEventPayload, 9 + } from 'react-native-gesture-handler' 10 + import EventEmitter from 'eventemitter3' 11 + 12 + export type GlobalGestureEvents = { 13 + begin: GestureStateChangeEvent<PanGestureHandlerEventPayload> 14 + update: GestureUpdateEvent<PanGestureHandlerEventPayload> 15 + end: GestureStateChangeEvent<PanGestureHandlerEventPayload> 16 + finalize: GestureStateChangeEvent<PanGestureHandlerEventPayload> 17 + } 18 + 19 + const Context = createContext<{ 20 + events: EventEmitter<GlobalGestureEvents> 21 + register: () => void 22 + unregister: () => void 23 + }>({ 24 + events: new EventEmitter<GlobalGestureEvents>(), 25 + register: () => {}, 26 + unregister: () => {}, 27 + }) 28 + 29 + export function GlobalGestureEventsProvider({ 30 + children, 31 + }: { 32 + children: React.ReactNode 33 + }) { 34 + const refCount = useRef(0) 35 + const events = useMemo(() => new EventEmitter<GlobalGestureEvents>(), []) 36 + const [enabled, setEnabled] = useState(false) 37 + const ctx = useMemo( 38 + () => ({ 39 + events, 40 + register() { 41 + refCount.current += 1 42 + if (refCount.current === 1) { 43 + setEnabled(true) 44 + } 45 + }, 46 + unregister() { 47 + refCount.current -= 1 48 + if (refCount.current === 0) { 49 + setEnabled(false) 50 + } 51 + }, 52 + }), 53 + [events, setEnabled], 54 + ) 55 + const gesture = Gesture.Pan() 56 + .runOnJS(true) 57 + .enabled(enabled) 58 + .simultaneousWithExternalGesture() 59 + .onBegin(e => { 60 + events.emit('begin', e) 61 + }) 62 + .onUpdate(e => { 63 + events.emit('update', e) 64 + }) 65 + .onEnd(e => { 66 + events.emit('end', e) 67 + }) 68 + .onFinalize(e => { 69 + events.emit('finalize', e) 70 + }) 71 + 72 + return ( 73 + <Context.Provider value={ctx}> 74 + <GestureDetector gesture={gesture}> 75 + <View collapsable={false}>{children}</View> 76 + </GestureDetector> 77 + </Context.Provider> 78 + ) 79 + } 80 + 81 + export function useGlobalGestureEvents() { 82 + return useContext(Context) 83 + }
+9
src/state/global-gesture-events/index.web.tsx
··· 1 + export function GlobalGestureEventsProvider(_props: { 2 + children: React.ReactNode 3 + }) { 4 + throw new Error('GlobalGestureEventsProvider is not supported on web.') 5 + } 6 + 7 + export function useGlobalGestureEvents() { 8 + throw new Error('useGlobalGestureEvents is not supported on web.') 9 + }
+11 -22
src/state/queries/explore-feed-previews.tsx
··· 36 36 const LIMIT = 8 // sliced to 6, overfetch to account for moderation 37 37 const PINNED_POST_URIS: Record<string, boolean> = { 38 38 // 📰 News 39 - 'at://did:plc:kkf4naxqmweop7dv4l2iqqf5/app.bsky.feed.post/3lgh27w2ngc2b': 40 - true, 39 + 'at://did:plc:kkf4naxqmweop7dv4l2iqqf5/app.bsky.feed.post/3lgh27w2ngc2b': true, 41 40 // Gardening 42 - 'at://did:plc:5rw2on4i56btlcajojaxwcat/app.bsky.feed.post/3kjorckgcwc27': 43 - true, 41 + 'at://did:plc:5rw2on4i56btlcajojaxwcat/app.bsky.feed.post/3kjorckgcwc27': true, 44 42 // Web Development Trending 45 - 'at://did:plc:m2sjv3wncvsasdapla35hzwj/app.bsky.feed.post/3lfaw445axs22': 46 - true, 43 + 'at://did:plc:m2sjv3wncvsasdapla35hzwj/app.bsky.feed.post/3lfaw445axs22': true, 47 44 // Anime & Manga EN 48 - 'at://did:plc:tazrmeme4dzahimsykusrwrk/app.bsky.feed.post/3knxx2gmkns2y': 49 - true, 45 + 'at://did:plc:tazrmeme4dzahimsykusrwrk/app.bsky.feed.post/3knxx2gmkns2y': true, 50 46 // 📽️ Film 51 - 'at://did:plc:2hwwem55ce6djnk6bn62cstr/app.bsky.feed.post/3llhpzhbq7c2g': 52 - true, 47 + 'at://did:plc:2hwwem55ce6djnk6bn62cstr/app.bsky.feed.post/3llhpzhbq7c2g': true, 53 48 // PopSky 54 - 'at://did:plc:lfdf4srj43iwdng7jn35tjsp/app.bsky.feed.post/3lbblgly65c2g': 55 - true, 49 + 'at://did:plc:lfdf4srj43iwdng7jn35tjsp/app.bsky.feed.post/3lbblgly65c2g': true, 56 50 // Science 57 - 'at://did:plc:hu2obebw3nhfj667522dahfg/app.bsky.feed.post/3kl33otd6ob2s': 58 - true, 51 + 'at://did:plc:hu2obebw3nhfj667522dahfg/app.bsky.feed.post/3kl33otd6ob2s': true, 59 52 // Birds! 🦉 60 - 'at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.post/3lbg4r57yk22d': 61 - true, 53 + 'at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.post/3lbg4r57yk22d': true, 62 54 // Astronomy 63 - 'at://did:plc:xy2zorw2ys47poflotxthlzg/app.bsky.feed.post/3kyzye4lujs2w': 64 - true, 55 + 'at://did:plc:xy2zorw2ys47poflotxthlzg/app.bsky.feed.post/3kyzye4lujs2w': true, 65 56 // What's Cooking 🍽️ 66 - 'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3lfqhgvxbqc2q': 67 - true, 57 + 'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3lfqhgvxbqc2q': true, 68 58 // BookSky 💙📚 #booksky 69 - 'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3kgrm2rw5ww2e': 70 - true, 59 + 'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3kgrm2rw5ww2e': true, 71 60 } 72 61 73 62 export type FeedPreviewItem =
+2 -3
src/state/queries/notifications/settings.ts
··· 36 36 mutationFn: async ( 37 37 update: Partial<AppBskyNotificationDefs.Preferences>, 38 38 ) => { 39 - const response = await agent.app.bsky.notification.putPreferencesV2( 40 - update, 41 - ) 39 + const response = 40 + await agent.app.bsky.notification.putPreferencesV2(update) 42 41 return response.data.preferences 43 42 }, 44 43 onMutate: update => {
+5 -5
src/state/queries/notifications/unread.tsx
··· 15 15 import {useModerationOpts} from '../../preferences/moderation-opts' 16 16 import {truncateAndInvalidate} from '../util' 17 17 import {RQKEY as RQKEY_NOTIFS} from './feed' 18 - import {CachedFeedPage, FeedPage} from './types' 18 + import {type CachedFeedPage, type FeedPage} from './types' 19 19 import {fetchPage} from './util' 20 20 21 21 const UPDATE_INTERVAL = 30 * 1e3 // 30sec ··· 96 96 data.event === '30+' 97 97 ? 30 98 98 : data.event === '' 99 - ? 0 100 - : parseInt(data.event, 10) || 1, 99 + ? 0 100 + : parseInt(data.event, 10) || 1, 101 101 } 102 102 setNumUnread(data.event) 103 103 } ··· 167 167 unreadCount >= 30 168 168 ? '30+' 169 169 : unreadCount === 0 170 - ? '' 171 - : String(unreadCount) 170 + ? '' 171 + : String(unreadCount) 172 172 173 173 // track last sync 174 174 const now = new Date()
+2 -2
src/state/queries/usePostThread/index.ts
··· 52 52 return view === 'linear' 53 53 ? LINEAR_VIEW_BELOW 54 54 : isWeb && gtPhone 55 - ? TREE_VIEW_BELOW_DESKTOP 56 - : TREE_VIEW_BELOW 55 + ? TREE_VIEW_BELOW_DESKTOP 56 + : TREE_VIEW_BELOW 57 57 }, [view, gtPhone]) 58 58 59 59 const postThreadQueryKey = createPostThreadQueryKey({
+1 -1
src/state/queries/usePostThread/traversal.ts
··· 444 444 const anchorPost = items.at(0) 445 445 const hasAnchorFromCache = anchorPost && anchorPost.type === 'threadPost' 446 446 const skeletonReplies = hasAnchorFromCache 447 - ? anchorPost.value.post.replyCount ?? 4 447 + ? (anchorPost.value.post.replyCount ?? 4) 448 448 : 4 449 449 450 450 if (!items.length) {
+5 -13
src/storage/index.ts
··· 1 1 import {useCallback, useEffect, useState} from 'react' 2 2 import {MMKV} from 'react-native-mmkv' 3 3 4 - import {Account, Device} from '#/storage/schema' 4 + import {type Account, type Device} from '#/storage/schema' 5 5 6 6 export * from '#/storage/schema' 7 7 ··· 83 83 } 84 84 } 85 85 86 - type StorageSchema<T extends Storage<any, any>> = T extends Storage< 87 - any, 88 - infer U 89 - > 90 - ? U 91 - : never 92 - type StorageScopes<T extends Storage<any, any>> = T extends Storage< 93 - infer S, 94 - any 95 - > 96 - ? S 97 - : never 86 + type StorageSchema<T extends Storage<any, any>> = 87 + T extends Storage<any, infer U> ? U : never 88 + type StorageScopes<T extends Storage<any, any>> = 89 + T extends Storage<infer S, any> ? S : never 98 90 99 91 /** 100 92 * Hook to use a storage instance. Acts like a useState hook, but persists the
+37 -1
src/style.css
··· 328 328 329 329 /* #/components/Select/index.web.tsx */ 330 330 .radix-select-content { 331 - box-shadow: 0px 6px 24px -10px rgba(22, 23, 24, 0.25), 331 + box-shadow: 332 + 0px 6px 24px -10px rgba(22, 23, 24, 0.25), 332 333 0px 6px 12px -12px rgba(22, 23, 24, 0.15); 333 334 min-width: var(--radix-select-trigger-width); 334 335 max-height: var(--radix-select-content-available-height); 335 336 } 337 + 338 + /* 339 + * #/components/Tooltip/index.web.tsx 340 + */ 341 + .radix-popover-content { 342 + animation-duration: 300ms; 343 + animation-timing-function: cubic-bezier(0.17, 0.73, 0.14, 1); 344 + will-change: transform, opacity; 345 + } 346 + .radix-popover-content[data-state='open'][data-side='top'] { 347 + animation-name: radixPopoverSlideUpAndFade; 348 + } 349 + .radix-popover-content[data-state='open'][data-side='bottom'] { 350 + animation-name: radixPopoverSlideDownAndFade; 351 + } 352 + @keyframes radixPopoverSlideUpAndFade { 353 + from { 354 + opacity: 0; 355 + transform: translateY(2px); 356 + } 357 + to { 358 + opacity: 1; 359 + transform: translateY(0); 360 + } 361 + } 362 + @keyframes radixPopoverSlideDownAndFade { 363 + from { 364 + opacity: 0; 365 + transform: translateY(-2px); 366 + } 367 + to { 368 + opacity: 1; 369 + transform: translateY(0); 370 + } 371 + }
+16 -16
src/view/com/composer/Composer.tsx
··· 518 518 thread.posts.length > 1 519 519 ? _(msg`Your posts have been published`) 520 520 : replyTo 521 - ? _(msg`Your reply has been published`) 522 - : _(msg`Your post has been published`), 521 + ? _(msg`Your reply has been published`) 522 + : _(msg`Your post has been published`), 523 523 ) 524 524 }, [ 525 525 _, ··· 1000 1000 }), 1001 1001 ) 1002 1002 : isThread 1003 - ? _( 1004 - msg({ 1005 - message: 'Publish posts', 1006 - comment: 1007 - 'Accessibility label for button to publish multiple posts in a thread', 1008 - }), 1009 - ) 1010 - : _( 1011 - msg({ 1012 - message: 'Publish post', 1013 - comment: 1014 - 'Accessibility label for button to publish a single post', 1015 - }), 1016 - ) 1003 + ? _( 1004 + msg({ 1005 + message: 'Publish posts', 1006 + comment: 1007 + 'Accessibility label for button to publish multiple posts in a thread', 1008 + }), 1009 + ) 1010 + : _( 1011 + msg({ 1012 + message: 'Publish post', 1013 + comment: 1014 + 'Accessibility label for button to publish a single post', 1015 + }), 1016 + ) 1017 1017 } 1018 1018 variant="solid" 1019 1019 color="primary"
+10 -10
src/view/com/composer/photos/Gallery.tsx
··· 1 1 import React from 'react' 2 2 import { 3 - ImageStyle, 3 + type ImageStyle, 4 4 Keyboard, 5 - LayoutChangeEvent, 5 + type LayoutChangeEvent, 6 6 StyleSheet, 7 7 TouchableOpacity, 8 8 View, 9 - ViewStyle, 9 + type ViewStyle, 10 10 } from 'react-native' 11 11 import {Image} from 'expo-image' 12 12 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' ··· 14 14 import {useLingui} from '@lingui/react' 15 15 16 16 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 17 - import {Dimensions} from '#/lib/media/types' 17 + import {type Dimensions} from '#/lib/media/types' 18 18 import {colors, s} from '#/lib/styles' 19 19 import {isNative} from '#/platform/detection' 20 - import {ComposerImage, cropImage} from '#/state/gallery' 20 + import {type ComposerImage, cropImage} from '#/state/gallery' 21 21 import {Text} from '#/view/com/util/text/Text' 22 22 import {useTheme} from '#/alf' 23 23 import * as Dialog from '#/components/Dialog' 24 - import {PostAction} from '../state/composer' 24 + import {type PostAction} from '../state/composer' 25 25 import {EditImageDialog} from './EditImageDialog' 26 26 import {ImageAltTextDialog} from './ImageAltTextDialog' 27 27 ··· 74 74 altTextControlStyle: isOverflow 75 75 ? {left: 4, bottom: 4} 76 76 : !isMobile && images.length < 3 77 - ? {left: 8, top: 8} 78 - : {left: 4, top: 4}, 77 + ? {left: 8, top: 8} 78 + : {left: 4, top: 4}, 79 79 imageControlsStyle: { 80 80 display: 'flex' as const, 81 81 flexDirection: 'row' as const, ··· 83 83 ...(isOverflow 84 84 ? {top: 4, right: 4, gap: 4} 85 85 : !isMobile && images.length < 3 86 - ? {top: 8, right: 8, gap: 8} 87 - : {top: 4, right: 4, gap: 4}), 86 + ? {top: 8, right: 8, gap: 8} 87 + : {top: 4, right: 4, gap: 4}), 88 88 zIndex: 1, 89 89 }, 90 90 imageStyle: {
+21 -16
src/view/com/composer/state/composer.ts
··· 1 - import {ImagePickerAsset} from 'expo-image-picker' 1 + import {type ImagePickerAsset} from 'expo-image-picker' 2 2 import { 3 - AppBskyFeedPostgate, 3 + type AppBskyFeedPostgate, 4 4 AppBskyRichtextFacet, 5 - BskyPreferences, 5 + type BskyPreferences, 6 6 RichText, 7 7 } from '@atproto/api' 8 8 import {nanoid} from 'nanoid/non-secure' 9 9 10 - import {SelfLabel} from '#/lib/moderation' 10 + import {type SelfLabel} from '#/lib/moderation' 11 11 import {insertMentionAt} from '#/lib/strings/mention-manip' 12 12 import {shortenLinks} from '#/lib/strings/rich-text-manip' 13 13 import { ··· 15 15 postUriToRelativePath, 16 16 toBskyAppUrl, 17 17 } from '#/lib/strings/url-helpers' 18 - import {ComposerImage, createInitialImages} from '#/state/gallery' 18 + import {type ComposerImage, createInitialImages} from '#/state/gallery' 19 19 import {createPostgateRecord} from '#/state/queries/postgate/util' 20 - import {Gif} from '#/state/queries/tenor' 20 + import {type Gif} from '#/state/queries/tenor' 21 21 import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate' 22 - import {ThreadgateAllowUISetting} from '#/state/queries/threadgate' 23 - import {ComposerOpts} from '#/state/shell/composer' 22 + import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate' 23 + import {type ComposerOpts} from '#/state/shell/composer' 24 24 import { 25 - LinkFacetMatch, 25 + type LinkFacetMatch, 26 26 suggestLinkCardUri, 27 27 } from '#/view/com/composer/text-input/text-input-util' 28 - import {createVideoState, VideoAction, videoReducer, VideoState} from './video' 28 + import { 29 + createVideoState, 30 + type VideoAction, 31 + videoReducer, 32 + type VideoState, 33 + } from './video' 29 34 30 35 type ImagesMedia = { 31 36 type: 'images' ··· 514 519 text: initText 515 520 ? initText 516 521 : initMention 517 - ? insertMentionAt( 518 - `@${initMention}`, 519 - initMention.length + 1, 520 - `${initMention}`, 521 - ) 522 - : '', 522 + ? insertMentionAt( 523 + `@${initMention}`, 524 + initMention.length + 1, 525 + `${initMention}`, 526 + ) 527 + : '', 523 528 }) 524 529 525 530 let link: Link | undefined
+2 -2
src/view/com/composer/text-input/web/Autocomplete.tsx
··· 209 209 itemIndex === 0 210 210 ? styles.firstMention 211 211 : itemIndex === totalItems - 1 212 - ? styles.lastMention 213 - : undefined, 212 + ? styles.lastMention 213 + : undefined, 214 214 ]} 215 215 onPress={onPress} 216 216 accessibilityRole="button">
+6 -6
src/view/com/lightbox/Lightbox.web.tsx
··· 1 1 import React, {useCallback, useEffect, useState} from 'react' 2 2 import { 3 3 Image, 4 - ImageStyle, 4 + type ImageStyle, 5 5 Pressable, 6 6 StyleSheet, 7 7 TouchableOpacity, 8 8 TouchableWithoutFeedback, 9 9 View, 10 - ViewStyle, 10 + type ViewStyle, 11 11 } from 'react-native' 12 12 import { 13 13 FontAwesomeIcon, 14 - FontAwesomeIconStyle, 14 + type FontAwesomeIconStyle, 15 15 } from '@fortawesome/react-native-fontawesome' 16 16 import {msg} from '@lingui/macro' 17 17 import {useLingui} from '@lingui/react' ··· 21 21 import {colors, s} from '#/lib/styles' 22 22 import {useLightbox, useLightboxControls} from '#/state/lightbox' 23 23 import {Text} from '../util/text/Text' 24 - import {ImageSource} from './ImageViewing/@types' 24 + import {type ImageSource} from './ImageViewing/@types' 25 25 import ImageDefaultHeader from './ImageViewing/components/ImageDefaultHeader' 26 26 27 27 export function Lightbox() { ··· 121 121 img.type === 'circle-avi' 122 122 ? '50%' 123 123 : img.type === 'rect-avi' 124 - ? '10%' 125 - : 0, 124 + ? '10%' 125 + : 0, 126 126 } as ImageStyle 127 127 } 128 128 alt={img.alt}
+2 -2
src/view/com/post-thread/PostThreadItem.tsx
··· 630 630 showChildReplyLine && !isThreadedChild 631 631 ? 0 632 632 : isThreadedChildAdjacentBot 633 - ? 4 634 - : 8, 633 + ? 4 634 + : 8, 635 635 }, 636 636 ]}> 637 637 {/* If we are in threaded mode, the avatar is rendered in PostMeta */}
+8 -4
src/view/com/posts/PostFeedErrorMessage.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, AppBskyFeedGetAuthorFeed, AtUri} from '@atproto/api' 3 + import { 4 + type AppBskyActorDefs, 5 + AppBskyFeedGetAuthorFeed, 6 + AtUri, 7 + } from '@atproto/api' 4 8 import {msg as msgLingui, Trans} from '@lingui/macro' 5 9 import {useLingui} from '@lingui/react' 6 10 import {useNavigation} from '@react-navigation/native' 7 11 8 12 import {usePalette} from '#/lib/hooks/usePalette' 9 - import {NavigationProp} from '#/lib/routes/types' 13 + import {type NavigationProp} from '#/lib/routes/types' 10 14 import {cleanError} from '#/lib/strings/errors' 11 15 import {logger} from '#/logger' 12 - import {FeedDescriptor} from '#/state/queries/post-feed' 16 + import {type FeedDescriptor} from '#/state/queries/post-feed' 13 17 import {useRemoveFeedMutation} from '#/state/queries/preferences' 14 18 import * as Prompt from '#/components/Prompt' 15 19 import {EmptyState} from '../util/EmptyState' ··· 119 123 [KnownError.FeedTooManyRequests]: _l( 120 124 msgLingui`This feed is currently receiving high traffic and is temporarily unavailable. Please try again later.`, 121 125 ), 122 - }[knownError]), 126 + })[knownError], 123 127 [_l, knownError], 124 128 ) 125 129 const [_, uri] = feedDesc.split('|')
+6 -6
src/view/com/profile/ProfileMenu.tsx
··· 495 495 msg`The account will be able to interact with you after unblocking.`, 496 496 ) 497 497 : profile.associated?.labeler 498 - ? _( 499 - msg`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.`, 500 - ) 501 - : _( 502 - msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 503 - ) 498 + ? _( 499 + msg`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.`, 500 + ) 501 + : _( 502 + msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 503 + ) 504 504 } 505 505 onConfirm={blockAccount} 506 506 confirmButtonCta={
+8 -3
src/view/com/util/List.web.tsx
··· 1 1 import React, {isValidElement, memo, startTransition, useRef} from 'react' 2 - import {FlatListProps, StyleSheet, View, ViewProps} from 'react-native' 3 - import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes' 2 + import { 3 + type FlatListProps, 4 + StyleSheet, 5 + View, 6 + type ViewProps, 7 + } from 'react-native' 8 + import {type ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes' 4 9 5 10 import {batchedUpdates} from '#/lib/batchedUpdates' 6 11 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' ··· 205 210 behavior: animated ? 'smooth' : 'instant', 206 211 }) 207 212 }, 208 - } as any), // TODO: Better types. 213 + }) as any, // TODO: Better types. 209 214 [getScrollableNode], 210 215 ) 211 216
+8 -8
src/view/screens/DebugMod.tsx
··· 113 113 }), 114 114 ] 115 115 : scenario[0] === 'label' && target[0] === 'profile' 116 - ? [ 117 - mock.label({ 118 - src: isSelfLabel ? did : undefined, 119 - val: label[0], 120 - uri: `at://${did}/app.bsky.actor.profile/self`, 121 - }), 122 - ] 123 - : undefined, 116 + ? [ 117 + mock.label({ 118 + src: isSelfLabel ? did : undefined, 119 + val: label[0], 120 + uri: `at://${did}/app.bsky.actor.profile/self`, 121 + }), 122 + ] 123 + : undefined, 124 124 viewer: mock.actorViewerState({ 125 125 following: isFollowing 126 126 ? `at://${currentAccount?.did || ''}/app.bsky.graph.follow/1234`
+61 -59
svgo.config.mjs
··· 1 1 const preset = [ 2 - "removeDoctype", 3 - "removeXMLProcInst", 4 - "removeComments", 5 - "removeMetadata", 6 - "removeEditorsNSData", 7 - "cleanupAttrs", 8 - "mergeStyles", 9 - "inlineStyles", 10 - "minifyStyles", 11 - "cleanupIds", 12 - "removeUselessDefs", 13 - "cleanupNumericValues", 14 - "convertColors", 15 - "removeUnknownsAndDefaults", 16 - "removeNonInheritableGroupAttrs", 17 - "removeUselessStrokeAndFill", 18 - "removeDimensions", 19 - "cleanupEnableBackground", 20 - "removeHiddenElems", 21 - "removeEmptyText", 22 - "convertShapeToPath", 23 - "convertEllipseToCircle", 24 - "moveElemsAttrsToGroup", 25 - "moveGroupAttrsToElems", 26 - "collapseGroups", 27 - "convertPathData", 28 - "convertTransform", 29 - "removeEmptyAttrs", 30 - "removeEmptyContainers", 31 - "removeUnusedNS", 32 - "mergePaths", 33 - "sortAttrs", 34 - "sortDefsChildren", 35 - "removeTitle", 36 - "removeDesc", 2 + 'removeDoctype', 3 + 'removeXMLProcInst', 4 + 'removeComments', 5 + 'removeMetadata', 6 + 'removeEditorsNSData', 7 + 'cleanupAttrs', 8 + 'mergeStyles', 9 + 'inlineStyles', 10 + 'minifyStyles', 11 + 'cleanupIds', 12 + 'removeUselessDefs', 13 + 'cleanupNumericValues', 14 + 'convertColors', 15 + 'removeUnknownsAndDefaults', 16 + 'removeNonInheritableGroupAttrs', 17 + 'removeUselessStrokeAndFill', 18 + 'removeDimensions', 19 + 'cleanupEnableBackground', 20 + 'removeHiddenElems', 21 + 'removeEmptyText', 22 + 'convertShapeToPath', 23 + 'convertEllipseToCircle', 24 + 'moveElemsAttrsToGroup', 25 + 'moveGroupAttrsToElems', 26 + 'collapseGroups', 27 + 'convertPathData', 28 + 'convertTransform', 29 + 'removeEmptyAttrs', 30 + 'removeEmptyContainers', 31 + 'removeUnusedNS', 32 + 'mergePaths', 33 + 'sortAttrs', 34 + 'sortDefsChildren', 35 + 'removeTitle', 36 + 'removeDesc', 37 37 ] 38 38 39 39 export default { 40 - plugins: [...preset.map(name => ({ 41 - name, 42 - params: { 43 - floatPrecision: 3, 44 - transformPrecision: 5, 45 - // minimise diff in ouput from svgomg 46 - // maybe remove in future? will produce smaller output 47 - convertToZ: false, 48 - removeUseless: false, 49 - } 50 - })), 51 - { 52 - name: 'addTrailingWhitespace', 53 - fn() { 54 - return { 55 - root: { 56 - exit (root) { 57 - root.children.push({ type: 'text', value: '\n' }) 58 - return root 59 - } 40 + plugins: [ 41 + ...preset.map(name => ({ 42 + name, 43 + params: { 44 + floatPrecision: 3, 45 + transformPrecision: 5, 46 + // minimise diff in ouput from svgomg 47 + // maybe remove in future? will produce smaller output 48 + convertToZ: false, 49 + removeUseless: false, 50 + }, 51 + })), 52 + { 53 + name: 'addTrailingWhitespace', 54 + fn() { 55 + return { 56 + root: { 57 + exit(root) { 58 + root.children.push({type: 'text', value: '\n'}) 59 + return root 60 + }, 61 + }, 60 62 } 61 - } 62 - } 63 - }] 64 - }; 63 + }, 64 + }, 65 + ], 66 + }
+4 -4
yarn.lock
··· 16220 16220 resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" 16221 16221 integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 16222 16222 16223 - prettier@^2.8.3: 16224 - version "2.8.8" 16225 - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" 16226 - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== 16223 + prettier@^3.6.0: 16224 + version "3.6.0" 16225 + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.0.tgz#18ec98d62cb0757a5d4eab40253ff3e6d0fc8dea" 16226 + integrity sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw== 16227 16227 16228 16228 pretty-bytes@^5.6.0: 16229 16229 version "5.6.0"