mirror of https://git.lenooby09.tech/LeNooby09/social-app.git

Compare changes

Choose any two refs to compare.

+2 -1
.eslintrc.js
··· 37 37 'Toast.Action', 38 38 'AgeAssuranceAdmonition', 39 39 'Span', 40 + 'StackedButton', 40 41 ], 41 42 impliedTextProps: [], 42 43 suggestedTextWrappers: { ··· 88 89 'no-unused-vars': 'off', 89 90 '@typescript-eslint/no-unused-vars': [ 90 91 'error', 91 - {argsIgnorePattern: '^_', varsIgnorePattern: '^_'}, 92 + {argsIgnorePattern: '^_', varsIgnorePattern: '^_.+'}, 92 93 ], 93 94 '@typescript-eslint/consistent-type-imports': [ 94 95 'warn',
+2
README.md
··· 70 70 71 71 See [./LICENSE](./LICENSE) for the full license. 72 72 73 + Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge). 74 + 73 75 ## P.S. 74 76 75 77 We โค๏ธ you and all of the ways you support us. Thank you for making Bluesky a great place!
+8 -12
jest/test-utils.tsx
··· 1 - import React from 'react' 2 - import {render} from '@testing-library/react-native' 3 1 import {GestureHandlerRootView} from 'react-native-gesture-handler' 4 - import {RootSiblingParent} from 'react-native-root-siblings' 5 2 import {SafeAreaProvider} from 'react-native-safe-area-context' 6 - import {RootStoreProvider, RootStoreModel} from '../src/state' 3 + import {render} from '@testing-library/react-native' 4 + 7 5 import {ThemeProvider} from '../src/lib/ThemeContext' 6 + import {type RootStoreModel, RootStoreProvider} from '../src/state' 8 7 9 8 const customRender = (ui: any, rootStore: RootStoreModel) => 10 9 render( 11 - // eslint-disable-next-line react-native/no-inline-styles 12 10 <GestureHandlerRootView style={{flex: 1}}> 13 - <RootSiblingParent> 14 - <RootStoreProvider value={rootStore}> 15 - <ThemeProvider theme="light"> 16 - <SafeAreaProvider>{ui}</SafeAreaProvider> 17 - </ThemeProvider> 18 - </RootStoreProvider> 19 - </RootSiblingParent> 11 + <RootStoreProvider value={rootStore}> 12 + <ThemeProvider theme="light"> 13 + <SafeAreaProvider>{ui}</SafeAreaProvider> 14 + </ThemeProvider> 15 + </RootStoreProvider> 20 16 </GestureHandlerRootView>, 21 17 ) 22 18
+2 -3
package.json
··· 197 197 "react-native-progress": "bluesky-social/react-native-progress", 198 198 "react-native-qrcode-styled": "^0.3.3", 199 199 "react-native-reanimated": "^3.19.1", 200 - "react-native-root-siblings": "^5.0.1", 201 200 "react-native-safe-area-context": "~5.6.0", 202 201 "react-native-screens": "~4.16.0", 203 202 "react-native-svg": "15.12.1", ··· 247 246 "babel-jest": "^29.7.0", 248 247 "babel-plugin-macros": "^3.1.0", 249 248 "babel-plugin-module-resolver": "^5.0.2", 250 - "babel-plugin-react-compiler": "^19.1.0-rc.1", 249 + "babel-plugin-react-compiler": "^19.1.0-rc.3", 251 250 "babel-preset-expo": "~54.0.0", 252 251 "eslint": "^8.19.0", 253 252 "eslint-plugin-bsky-internal": "link:./eslint", ··· 255 254 "eslint-plugin-import": "^2.31.0", 256 255 "eslint-plugin-lingui": "^0.2.0", 257 256 "eslint-plugin-react": "^7.33.2", 258 - "eslint-plugin-react-compiler": "^19.1.0-rc.1", 257 + "eslint-plugin-react-compiler": "^19.1.0-rc.2", 259 258 "eslint-plugin-react-native-a11y": "^3.3.0", 260 259 "eslint-plugin-simple-import-sort": "^12.0.0", 261 260 "file-loader": "6.2.0",
+61 -60
src/App.native.tsx
··· 4 4 5 5 import React, {useEffect, useState} from 'react' 6 6 import {GestureHandlerRootView} from 'react-native-gesture-handler' 7 - import {RootSiblingParent} from 'react-native-root-siblings' 8 7 import { 9 8 initialWindowMetrics, 10 9 SafeAreaProvider, ··· 84 83 } 85 84 if (isAndroid) { 86 85 // iOS is handled by the config plugin -sfn 87 - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) 86 + ScreenOrientation.lockAsync( 87 + ScreenOrientation.OrientationLock.PORTRAIT_UP, 88 + ).catch(error => 89 + logger.debug('Could not lock orientation', {safeMessage: error}), 90 + ) 88 91 } 89 92 90 93 /** ··· 133 136 <ThemeProvider theme={theme}> 134 137 <ContextMenuProvider> 135 138 <Splash isReady={isReady && hasCheckedReferrer}> 136 - <RootSiblingParent> 137 - <VideoVolumeProvider> 138 - <React.Fragment 139 - // Resets the entire tree below when it changes: 140 - key={currentAccount?.did}> 141 - <QueryProvider currentDid={currentAccount?.did}> 142 - <PolicyUpdateOverlayProvider> 143 - <StatsigProvider> 144 - <AgeAssuranceProvider> 145 - <ComposerProvider> 146 - <MessagesProvider> 147 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 148 - <LabelDefsProvider> 149 - <ModerationOptsProvider> 150 - <LoggedOutViewProvider> 151 - <SelectedFeedProvider> 152 - <HiddenRepliesProvider> 153 - <HomeBadgeProvider> 154 - <UnreadNotifsProvider> 155 - <BackgroundNotificationPreferencesProvider> 156 - <MutedThreadsProvider> 157 - <ProgressGuideProvider> 158 - <ServiceAccountManager> 159 - <EmailVerificationProvider> 160 - <HideBottomBarBorderProvider> 161 - <GestureHandlerRootView 162 - style={s.h100pct}> 163 - <GlobalGestureEventsProvider> 164 - <IntentDialogProvider> 165 - <TestCtrls /> 166 - <Shell /> 167 - <NuxDialogs /> 168 - <ToastOutlet /> 169 - </IntentDialogProvider> 170 - </GlobalGestureEventsProvider> 171 - </GestureHandlerRootView> 172 - </HideBottomBarBorderProvider> 173 - </EmailVerificationProvider> 174 - </ServiceAccountManager> 175 - </ProgressGuideProvider> 176 - </MutedThreadsProvider> 177 - </BackgroundNotificationPreferencesProvider> 178 - </UnreadNotifsProvider> 179 - </HomeBadgeProvider> 180 - </HiddenRepliesProvider> 181 - </SelectedFeedProvider> 182 - </LoggedOutViewProvider> 183 - </ModerationOptsProvider> 184 - </LabelDefsProvider> 185 - </MessagesProvider> 186 - </ComposerProvider> 187 - </AgeAssuranceProvider> 188 - </StatsigProvider> 189 - </PolicyUpdateOverlayProvider> 190 - </QueryProvider> 191 - </React.Fragment> 192 - </VideoVolumeProvider> 193 - </RootSiblingParent> 139 + <VideoVolumeProvider> 140 + <React.Fragment 141 + // Resets the entire tree below when it changes: 142 + key={currentAccount?.did}> 143 + <QueryProvider currentDid={currentAccount?.did}> 144 + <PolicyUpdateOverlayProvider> 145 + <StatsigProvider> 146 + <AgeAssuranceProvider> 147 + <ComposerProvider> 148 + <MessagesProvider> 149 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 150 + <LabelDefsProvider> 151 + <ModerationOptsProvider> 152 + <LoggedOutViewProvider> 153 + <SelectedFeedProvider> 154 + <HiddenRepliesProvider> 155 + <HomeBadgeProvider> 156 + <UnreadNotifsProvider> 157 + <BackgroundNotificationPreferencesProvider> 158 + <MutedThreadsProvider> 159 + <ProgressGuideProvider> 160 + <ServiceAccountManager> 161 + <EmailVerificationProvider> 162 + <HideBottomBarBorderProvider> 163 + <GestureHandlerRootView 164 + style={s.h100pct}> 165 + <GlobalGestureEventsProvider> 166 + <IntentDialogProvider> 167 + <TestCtrls /> 168 + <Shell /> 169 + <NuxDialogs /> 170 + <ToastOutlet /> 171 + </IntentDialogProvider> 172 + </GlobalGestureEventsProvider> 173 + </GestureHandlerRootView> 174 + </HideBottomBarBorderProvider> 175 + </EmailVerificationProvider> 176 + </ServiceAccountManager> 177 + </ProgressGuideProvider> 178 + </MutedThreadsProvider> 179 + </BackgroundNotificationPreferencesProvider> 180 + </UnreadNotifsProvider> 181 + </HomeBadgeProvider> 182 + </HiddenRepliesProvider> 183 + </SelectedFeedProvider> 184 + </LoggedOutViewProvider> 185 + </ModerationOptsProvider> 186 + </LabelDefsProvider> 187 + </MessagesProvider> 188 + </ComposerProvider> 189 + </AgeAssuranceProvider> 190 + </StatsigProvider> 191 + </PolicyUpdateOverlayProvider> 192 + </QueryProvider> 193 + </React.Fragment> 194 + </VideoVolumeProvider> 194 195 </Splash> 195 196 </ContextMenuProvider> 196 197 </ThemeProvider>
+54 -57
src/App.web.tsx
··· 3 3 import './style.css' 4 4 5 5 import React, {useEffect, useState} from 'react' 6 - import {RootSiblingParent} from 'react-native-root-siblings' 7 6 import {SafeAreaProvider} from 'react-native-safe-area-context' 8 7 import {msg} from '@lingui/macro' 9 8 import {useLingui} from '@lingui/react' ··· 111 110 <Alf theme={theme}> 112 111 <ThemeProvider theme={theme}> 113 112 <ContextMenuProvider> 114 - <RootSiblingParent> 115 - <VideoVolumeProvider> 116 - <ActiveVideoProvider> 117 - <React.Fragment 118 - // Resets the entire tree below when it changes: 119 - key={currentAccount?.did}> 120 - <QueryProvider currentDid={currentAccount?.did}> 121 - <PolicyUpdateOverlayProvider> 122 - <StatsigProvider> 123 - <AgeAssuranceProvider> 124 - <ComposerProvider> 125 - <MessagesProvider> 126 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 127 - <LabelDefsProvider> 128 - <ModerationOptsProvider> 129 - <LoggedOutViewProvider> 130 - <SelectedFeedProvider> 131 - <HiddenRepliesProvider> 132 - <HomeBadgeProvider> 133 - <UnreadNotifsProvider> 134 - <BackgroundNotificationPreferencesProvider> 135 - <MutedThreadsProvider> 136 - <SafeAreaProvider> 137 - <ProgressGuideProvider> 138 - <ServiceConfigProvider> 139 - <EmailVerificationProvider> 140 - <HideBottomBarBorderProvider> 141 - <IntentDialogProvider> 142 - <Shell /> 143 - <NuxDialogs /> 144 - <ToastOutlet /> 145 - </IntentDialogProvider> 146 - </HideBottomBarBorderProvider> 147 - </EmailVerificationProvider> 148 - </ServiceConfigProvider> 149 - </ProgressGuideProvider> 150 - </SafeAreaProvider> 151 - </MutedThreadsProvider> 152 - </BackgroundNotificationPreferencesProvider> 153 - </UnreadNotifsProvider> 154 - </HomeBadgeProvider> 155 - </HiddenRepliesProvider> 156 - </SelectedFeedProvider> 157 - </LoggedOutViewProvider> 158 - </ModerationOptsProvider> 159 - </LabelDefsProvider> 160 - </MessagesProvider> 161 - </ComposerProvider> 162 - </AgeAssuranceProvider> 163 - </StatsigProvider> 164 - </PolicyUpdateOverlayProvider> 165 - </QueryProvider> 166 - </React.Fragment> 167 - </ActiveVideoProvider> 168 - </VideoVolumeProvider> 169 - </RootSiblingParent> 113 + <VideoVolumeProvider> 114 + <ActiveVideoProvider> 115 + <React.Fragment 116 + // Resets the entire tree below when it changes: 117 + key={currentAccount?.did}> 118 + <QueryProvider currentDid={currentAccount?.did}> 119 + <PolicyUpdateOverlayProvider> 120 + <StatsigProvider> 121 + <AgeAssuranceProvider> 122 + <ComposerProvider> 123 + <MessagesProvider> 124 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 125 + <LabelDefsProvider> 126 + <ModerationOptsProvider> 127 + <LoggedOutViewProvider> 128 + <SelectedFeedProvider> 129 + <HiddenRepliesProvider> 130 + <HomeBadgeProvider> 131 + <UnreadNotifsProvider> 132 + <BackgroundNotificationPreferencesProvider> 133 + <MutedThreadsProvider> 134 + <SafeAreaProvider> 135 + <ProgressGuideProvider> 136 + <ServiceConfigProvider> 137 + <EmailVerificationProvider> 138 + <HideBottomBarBorderProvider> 139 + <IntentDialogProvider> 140 + <Shell /> 141 + <NuxDialogs /> 142 + <ToastOutlet /> 143 + </IntentDialogProvider> 144 + </HideBottomBarBorderProvider> 145 + </EmailVerificationProvider> 146 + </ServiceConfigProvider> 147 + </ProgressGuideProvider> 148 + </SafeAreaProvider> 149 + </MutedThreadsProvider> 150 + </BackgroundNotificationPreferencesProvider> 151 + </UnreadNotifsProvider> 152 + </HomeBadgeProvider> 153 + </HiddenRepliesProvider> 154 + </SelectedFeedProvider> 155 + </LoggedOutViewProvider> 156 + </ModerationOptsProvider> 157 + </LabelDefsProvider> 158 + </MessagesProvider> 159 + </ComposerProvider> 160 + </AgeAssuranceProvider> 161 + </StatsigProvider> 162 + </PolicyUpdateOverlayProvider> 163 + </QueryProvider> 164 + </React.Fragment> 165 + </ActiveVideoProvider> 166 + </VideoVolumeProvider> 170 167 </ContextMenuProvider> 171 168 </ThemeProvider> 172 169 </Alf>
+10 -4
src/alf/util/systemUI.ts
··· 1 1 import * as SystemUI from 'expo-system-ui' 2 2 import {type Theme} from '@bsky.app/alf' 3 3 4 + import {logger} from '#/logger' 4 5 import {isAndroid} from '#/platform/detection' 5 6 6 7 export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { 7 8 if (isAndroid) { 8 - if (themeType === 'theme') { 9 - SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 10 - } else { 11 - SystemUI.setBackgroundColorAsync('black') 9 + try { 10 + if (themeType === 'theme') { 11 + SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 12 + } else { 13 + SystemUI.setBackgroundColorAsync('black') 14 + } 15 + } catch (error) { 16 + // Can reject with 'The current activity is no longer available' - no big deal 17 + logger.debug('Could not set system UI theme', {safeMessage: error}) 12 18 } 13 19 } 14 20 }
+44 -24
src/components/Admonition.tsx
··· 3 3 4 4 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 5 5 import {Button as BaseButton, type ButtonProps} from '#/components/Button' 6 - import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' 7 - import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye' 8 - import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf' 6 + import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' 7 + import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' 9 8 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 10 9 import {Text as BaseText, type TextProps} from '#/components/Typography' 11 10 12 11 export const colors = { 13 - warning: { 14 - light: '#DFBC00', 15 - dark: '#BFAF1F', 16 - }, 12 + warning: '#FFC404', 17 13 } 18 14 19 15 type Context = { ··· 29 25 const t = useTheme() 30 26 const {type} = useContext(Context) 31 27 const Icon = { 32 - info: InfoIcon, 33 - tip: TipIcon, 28 + info: CircleInfoIcon, 29 + tip: CircleInfoIcon, 34 30 warning: WarningIcon, 35 - error: ErrorIcon, 31 + error: CircleXIcon, 36 32 }[type] 37 33 const fill = { 38 34 info: t.atoms.text_contrast_medium.color, 39 35 tip: t.palette.primary_500, 40 - warning: colors.warning.light, 36 + warning: colors.warning, 41 37 error: t.palette.negative_500, 42 38 }[type] 43 39 return <Icon fill={fill} size="md" /> 44 40 } 45 41 42 + export function Content({ 43 + children, 44 + style, 45 + ...rest 46 + }: { 47 + children: React.ReactNode 48 + style?: StyleProp<ViewStyle> 49 + }) { 50 + return ( 51 + <View 52 + style={[a.gap_sm, a.flex_1, {minHeight: 20}, a.justify_center, style]} 53 + {...rest}> 54 + {children} 55 + </View> 56 + ) 57 + } 58 + 46 59 export function Text({ 47 60 children, 48 61 style, 49 62 ...rest 50 63 }: Pick<TextProps, 'children' | 'style'>) { 51 64 return ( 52 - <BaseText 53 - {...rest} 54 - style={[a.flex_1, a.text_sm, a.leading_snug, a.pr_md, style]}> 65 + <BaseText {...rest} style={[a.text_sm, a.leading_snug, a.pr_md, style]}> 55 66 {children} 56 67 </BaseText> 57 68 ) ··· 60 71 export function Button({ 61 72 children, 62 73 ...props 63 - }: Omit<ButtonProps, 'size' | 'variant' | 'color'>) { 74 + }: Omit<ButtonProps, 'size' | 'variant'>) { 64 75 return ( 65 - <BaseButton size="tiny" variant="outline" color="secondary" {...props}> 76 + <BaseButton size="tiny" {...props}> 66 77 {children} 67 78 </BaseButton> 68 79 ) 69 80 } 70 81 71 - export function Row({children}: {children: React.ReactNode}) { 82 + export function Row({ 83 + children, 84 + style, 85 + }: { 86 + children: React.ReactNode 87 + style?: StyleProp<ViewStyle> 88 + }) { 72 89 return ( 73 - <View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 90 + <View style={[a.flex_1, a.flex_row, a.align_start, a.gap_sm, style]}> 74 91 {children} 75 92 </View> 76 93 ) ··· 88 105 const t = useTheme() 89 106 const {gtMobile} = useBreakpoints() 90 107 const borderColor = { 91 - info: t.atoms.border_contrast_low.borderColor, 92 - tip: t.atoms.border_contrast_low.borderColor, 93 - warning: t.atoms.border_contrast_low.borderColor, 94 - error: t.atoms.border_contrast_low.borderColor, 108 + info: t.atoms.border_contrast_high.borderColor, 109 + tip: t.palette.primary_500, 110 + warning: colors.warning, 111 + error: t.palette.negative_500, 95 112 }[type] 96 113 return ( 97 114 <Context.Provider value={{type}}> 98 115 <View 99 116 style={[ 100 117 gtMobile ? a.p_md : a.p_sm, 118 + a.p_md, 101 119 a.rounded_sm, 102 120 a.border, 103 - t.atoms.bg_contrast_25, 121 + t.atoms.bg, 104 122 {borderColor}, 105 123 style, 106 124 ]}> ··· 123 141 <Outer type={type} style={style}> 124 142 <Row> 125 143 <Icon /> 126 - <Text>{children}</Text> 144 + <Content> 145 + <Text>{children}</Text> 146 + </Content> 127 147 </Row> 128 148 </Outer> 129 149 )
+53 -41
src/components/Button.tsx
··· 274 274 } else if (color === 'primary_subtle') { 275 275 if (!disabled) { 276 276 baseStyles.push({ 277 - backgroundColor: select(t.name, { 278 - light: t.palette.primary_50, 279 - dim: t.palette.primary_100, 280 - dark: t.palette.primary_100, 281 - }), 277 + backgroundColor: t.palette.primary_50, 282 278 }) 283 279 hoverStyles.push({ 284 - backgroundColor: select(t.name, { 285 - light: t.palette.primary_100, 286 - dim: t.palette.primary_200, 287 - dark: t.palette.primary_200, 288 - }), 280 + backgroundColor: t.palette.primary_100, 289 281 }) 290 282 } else { 291 283 baseStyles.push({ ··· 295 287 } else if (color === 'negative_subtle') { 296 288 if (!disabled) { 297 289 baseStyles.push({ 298 - backgroundColor: select(t.name, { 299 - light: t.palette.negative_50, 300 - dim: t.palette.negative_100, 301 - dark: t.palette.negative_100, 302 - }), 290 + backgroundColor: t.palette.negative_50, 303 291 }) 304 292 hoverStyles.push({ 305 - backgroundColor: select(t.name, { 306 - light: t.palette.negative_100, 307 - dim: t.palette.negative_200, 308 - dark: t.palette.negative_200, 309 - }), 293 + backgroundColor: t.palette.negative_100, 310 294 }) 311 295 } else { 312 296 baseStyles.push({ ··· 618 602 } else if (color === 'primary_subtle') { 619 603 if (!disabled) { 620 604 baseStyles.push({ 621 - color: select(t.name, { 622 - light: t.palette.primary_600, 623 - dim: t.palette.primary_800, 624 - dark: t.palette.primary_800, 625 - }), 605 + color: t.palette.primary_600, 626 606 }) 627 607 } else { 628 608 baseStyles.push({ 629 - color: select(t.name, { 630 - light: t.palette.primary_200, 631 - dim: t.palette.primary_200, 632 - dark: t.palette.primary_200, 633 - }), 609 + color: t.palette.primary_200, 634 610 }) 635 611 } 636 612 } else if (color === 'negative_subtle') { 637 613 if (!disabled) { 638 614 baseStyles.push({ 639 - color: select(t.name, { 640 - light: t.palette.negative_600, 641 - dim: t.palette.negative_800, 642 - dark: t.palette.negative_800, 643 - }), 615 + color: t.palette.negative_600, 644 616 }) 645 617 } else { 646 618 baseStyles.push({ 647 - color: select(t.name, { 648 - light: t.palette.negative_200, 649 - dim: t.palette.negative_200, 650 - dark: t.palette.negative_200, 651 - }), 619 + color: t.palette.negative_200, 652 620 }) 653 621 } 654 622 } ··· 755 723 } else if (size === 'small') { 756 724 baseStyles.push(a.text_sm, a.leading_snug, a.font_medium) 757 725 } else if (size === 'tiny') { 758 - baseStyles.push(a.text_xs, a.leading_snug, a.font_medium) 726 + baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold) 759 727 } 760 728 761 729 return StyleSheet.flatten(baseStyles) ··· 869 837 </View> 870 838 ) 871 839 } 840 + 841 + export type StackedButtonProps = Omit< 842 + ButtonProps, 843 + keyof VariantProps | 'children' 844 + > & 845 + Pick<VariantProps, 'color'> & { 846 + children: React.ReactNode 847 + icon: React.ComponentType<SVGIconProps> 848 + } 849 + 850 + export function StackedButton({children, ...props}: StackedButtonProps) { 851 + return ( 852 + <Button 853 + {...props} 854 + size="tiny" 855 + style={[ 856 + a.flex_col, 857 + { 858 + height: 72, 859 + paddingHorizontal: 16, 860 + borderRadius: 20, 861 + gap: 4, 862 + }, 863 + props.style, 864 + ]}> 865 + <StackedButtonInnerText icon={props.icon}> 866 + {children} 867 + </StackedButtonInnerText> 868 + </Button> 869 + ) 870 + } 871 + 872 + function StackedButtonInnerText({ 873 + children, 874 + icon: Icon, 875 + }: Pick<StackedButtonProps, 'icon' | 'children'>) { 876 + const textStyles = useSharedButtonTextStyles() 877 + return ( 878 + <> 879 + <Icon width={24} fill={textStyles.color} /> 880 + <ButtonText>{children}</ButtonText> 881 + </> 882 + ) 883 + }
+2 -2
src/components/Link.tsx
··· 165 165 if (isNative && screen !== 'NotFound') { 166 166 const state = navigation.getState() 167 167 // if screen is not in the current navigator, it means it's 168 - // most likely a tab screen 169 - if (!state.routeNames.includes(screen)) { 168 + // most likely a tab screen. note: state can be undefined 169 + if (!state?.routeNames.includes(screen)) { 170 170 const parent = navigation.getParent() 171 171 if ( 172 172 parent &&
-1
src/components/PostControls/ShareMenu/RecentChats.tsx
··· 24 24 25 25 export function RecentChats({postUri}: {postUri: string}) { 26 26 const control = useDialogContext() 27 - const {_} = useLingui() 28 27 const {currentAccount} = useSession() 29 28 const {data} = useListConvosQuery({status: 'accepted'}) 30 29 const convos = data?.pages[0]?.convos?.slice(0, 10)
-1
src/components/dialogs/StarterPackDialog.tsx
··· 47 47 targetDid, 48 48 enabled, 49 49 }: StarterPackDialogProps) { 50 - const {_} = useLingui() 51 50 const navigation = useNavigation<NavigationProp>() 52 51 const requireEmailVerification = useRequireEmailVerification() 53 52
+3 -1
src/components/moderation/LabelsOnMeDialog.tsx
··· 32 32 33 33 export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) { 34 34 return ( 35 - <Dialog.Outer control={props.control}> 35 + <Dialog.Outer 36 + control={props.control} 37 + nativeOptions={{preventExpansion: true}}> 36 38 <Dialog.Handle /> 37 39 <LabelsOnMeDialogInner {...props} /> 38 40 </Dialog.Outer>
+3 -1
src/components/moderation/ModerationDetailsDialog.tsx
··· 24 24 25 25 export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) { 26 26 return ( 27 - <Dialog.Outer control={props.control}> 27 + <Dialog.Outer 28 + control={props.control} 29 + nativeOptions={{preventExpansion: true}}> 28 30 <Dialog.Handle /> 29 31 <ModerationDetailsDialogInner {...props} /> 30 32 </Dialog.Outer>
+2 -1
src/components/moderation/ProfileHeaderAlerts.tsx
··· 6 6 7 7 export function ProfileHeaderAlerts({ 8 8 moderation, 9 + style, 9 10 }: { 10 11 moderation: ModerationDecision 11 12 style?: StyleProp<ViewStyle> ··· 16 17 } 17 18 18 19 return ( 19 - <Pills.Row size="lg"> 20 + <Pills.Row size="lg" style={style}> 20 21 {modui.alerts.filter(unique).map(cause => ( 21 22 <Pills.Label 22 23 size="lg"
+6 -3
src/components/moderation/ReportDialog/index.tsx
··· 219 219 <Admonition.Outer type="error"> 220 220 <Admonition.Row> 221 221 <Admonition.Icon /> 222 - <Admonition.Text> 223 - <Trans>Something went wrong, please try again</Trans> 224 - </Admonition.Text> 222 + <Admonition.Content> 223 + <Admonition.Text> 224 + <Trans>Something went wrong, please try again</Trans> 225 + </Admonition.Text> 226 + </Admonition.Content> 225 227 <Admonition.Button 228 + color="negative_subtle" 226 229 label={_(msg`Retry loading report options`)} 227 230 onPress={() => refetchLabelers()}> 228 231 <ButtonText>
+1 -1
src/lib/hooks/useIntentHandler.ts
··· 51 51 } 52 52 53 53 const urlp = new URL(url) 54 - const [_, intent, intentType] = urlp.pathname.split('/') 54 + const [__, intent, intentType] = urlp.pathname.split('/') 55 55 56 56 // On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the 57 57 // intent check. On web, we have to check the first part of the path since we have an actual hostname
+9 -4
src/lib/hooks/useOTAUpdates.ts
··· 10 10 useUpdates, 11 11 } from 'expo-updates' 12 12 13 + import {isNetworkError} from '#/lib/strings/errors' 13 14 import {logger} from '#/logger' 14 15 import {isIOS} from '#/platform/detection' 15 16 import {IS_TESTFLIGHT} from '#/env' ··· 145 146 } else { 146 147 logger.debug('No update available.') 147 148 } 148 - } catch (e) { 149 - logger.error('OTA Update Error', {error: `${e}`}) 149 + } catch (err) { 150 + if (!isNetworkError(err)) { 151 + logger.error('OTA Update Error', {safeMessage: err}) 152 + } 150 153 } 151 154 }, 10e3) 152 155 }, []) ··· 154 157 const onIsTestFlight = React.useCallback(async () => { 155 158 try { 156 159 await updateTestflight() 157 - } catch (e: any) { 158 - logger.error('Internal OTA Update Error', {error: `${e}`}) 160 + } catch (err: any) { 161 + if (!isNetworkError(err)) { 162 + logger.error('Internal OTA Update Error', {safeMessage: err}) 163 + } 159 164 } 160 165 }, []) 161 166
+11 -11
src/lib/strings/embed-player.ts
··· 105 105 urlp.hostname === 'm.youtube.com' || 106 106 urlp.hostname === 'music.youtube.com' 107 107 ) { 108 - const [_, page, shortOrLiveVideoId] = urlp.pathname.split('/') 108 + const [__, page, shortOrLiveVideoId] = urlp.pathname.split('/') 109 109 110 110 const isShorts = page === 'shorts' 111 111 const isLive = page === 'live' ··· 137 137 window.location.hostname 138 138 : 'localhost' 139 139 140 - const [_, channelOrVideo, clipOrId, id] = urlp.pathname.split('/') 140 + const [__, channelOrVideo, clipOrId, id] = urlp.pathname.split('/') 141 141 142 142 if (channelOrVideo === 'videos') { 143 143 return { ··· 162 162 163 163 // spotify 164 164 if (urlp.hostname === 'open.spotify.com') { 165 - const [_, typeOrLocale, idOrType, id] = urlp.pathname.split('/') 165 + const [__, typeOrLocale, idOrType, id] = urlp.pathname.split('/') 166 166 167 167 if (idOrType) { 168 168 if (typeOrLocale === 'playlist' || idOrType === 'playlist') { ··· 210 210 urlp.hostname === 'soundcloud.com' || 211 211 urlp.hostname === 'www.soundcloud.com' 212 212 ) { 213 - const [_, user, trackOrSets, set] = urlp.pathname.split('/') 213 + const [__, user, trackOrSets, set] = urlp.pathname.split('/') 214 214 215 215 if (user && trackOrSets) { 216 216 if (trackOrSets === 'sets' && set) { ··· 270 270 } 271 271 272 272 if (urlp.hostname === 'vimeo.com' || urlp.hostname === 'www.vimeo.com') { 273 - const [_, videoId] = urlp.pathname.split('/') 273 + const [__, videoId] = urlp.pathname.split('/') 274 274 if (videoId) { 275 275 return { 276 276 type: 'vimeo_video', ··· 281 281 } 282 282 283 283 if (urlp.hostname === 'giphy.com' || urlp.hostname === 'www.giphy.com') { 284 - const [_, gifs, nameAndId] = urlp.pathname.split('/') 284 + const [__, gifs, nameAndId] = urlp.pathname.split('/') 285 285 286 286 /* 287 287 * nameAndId is a string that consists of the name (dash separated) and the id of the gif (the last part of the name) ··· 309 309 // These can include (presumably) a tracking id in the path name, so we have to check for that as well 310 310 if (giphyRegex.test(urlp.hostname)) { 311 311 // We can link directly to the gif, if its a proper link 312 - const [_, media, trackingOrId, idOrFilename, filename] = 312 + const [__, media, trackingOrId, idOrFilename, filename] = 313 313 urlp.pathname.split('/') 314 314 315 315 if (media === 'media') { ··· 338 338 // Finally, we should see if it is a link to i.giphy.com. These links don't necessarily end in .gif but can also 339 339 // be .webp 340 340 if (urlp.hostname === 'i.giphy.com' || urlp.hostname === 'www.i.giphy.com') { 341 - const [_, mediaOrFilename, filename] = urlp.pathname.split('/') 341 + const [__, mediaOrFilename, filename] = urlp.pathname.split('/') 342 342 343 343 if (mediaOrFilename === 'media' && filename) { 344 344 const gifId = filename.split('.')[0] ··· 389 389 const path_components = urlp.pathname.slice(1, i + 1).split('/') 390 390 if (path_components.length === 4) { 391 391 // discard username - it's not relevant 392 - const [photos, _, albums, id] = path_components 392 + const [photos, __, albums, id] = path_components 393 393 if (photos === 'photos' && albums === 'albums') { 394 394 // this at least has the shape of a valid photo-album URL! 395 395 return { ··· 417 417 // link shortened flickr path 418 418 if (urlp.hostname === 'flic.kr') { 419 419 const b58alph = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' 420 - let [_, type, idBase58Enc] = urlp.pathname.split('/') 420 + let [__, type, idBase58Enc] = urlp.pathname.split('/') 421 421 let id = 0n 422 422 for (const char of idBase58Enc) { 423 423 const nextIdx = b58alph.indexOf(char) ··· 528 528 return {success: false} 529 529 } 530 530 531 - let [_, id, filename] = urlp.pathname.split('/') 531 + let [__, id, filename] = urlp.pathname.split('/') 532 532 533 533 if (!id || !filename) { 534 534 return {success: false}
-17
src/lib/strings/helpers.ts
··· 62 62 }, [splitter, maxCount, text]) 63 63 } 64 64 65 - // https://stackoverflow.com/a/52171480 66 - export function toHashCode(str: string, seed = 0): number { 67 - let h1 = 0xdeadbeef ^ seed, 68 - h2 = 0x41c6ce57 ^ seed 69 - for (let i = 0, ch; i < str.length; i++) { 70 - ch = str.charCodeAt(i) 71 - h1 = Math.imul(h1 ^ ch, 2654435761) 72 - h2 = Math.imul(h2 ^ ch, 1597334677) 73 - } 74 - h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) 75 - h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909) 76 - h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) 77 - h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909) 78 - 79 - return 4294967296 * (2097151 & h2) + (h1 >>> 0) 80 - } 81 - 82 65 export function countLines(str: string | undefined): number { 83 66 if (!str) return 0 84 67 return str.match(/\n/g)?.length ?? 0
+1 -1
src/lib/strings/starter-pack.ts
··· 46 46 } else { 47 47 const url = new URL(uri) 48 48 const parts = url.pathname.split('/') 49 - const [_, path, name, rkey] = parts 49 + const [__, path, name, rkey] = parts 50 50 51 51 if (parts.length !== 4) return null 52 52 if (path !== 'starter-pack' && path !== 'start') return null
+225 -215
src/locale/locales/en/messages.po
··· 124 124 msgid "{0, plural, other {# people have}} used this starter pack!" 125 125 msgstr "" 126 126 127 - #: src/components/dialogs/StarterPackDialog.tsx:357 127 + #: src/components/dialogs/StarterPackDialog.tsx:356 128 128 msgid "{0, plural, other {+# more}}" 129 129 msgstr "" 130 130 ··· 521 521 msgid "7 days" 522 522 msgstr "" 523 523 524 - #: src/screens/Onboarding/StepFinished.tsx:341 524 + #: src/screens/Onboarding/StepFinished.tsx:340 525 525 msgid "A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens" 526 526 msgstr "" 527 527 ··· 568 568 msgid "Accept Request" 569 569 msgstr "" 570 570 571 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:179 572 + msgid "Accept this language suggestion" 573 + msgstr "" 574 + 571 575 #: src/screens/Settings/AccessibilitySettings.tsx:44 572 576 #: src/screens/Settings/Settings.tsx:220 573 577 #: src/screens/Settings/Settings.tsx:223 ··· 604 608 msgid "Account muted" 605 609 msgstr "" 606 610 607 - #: src/components/moderation/ModerationDetailsDialog.tsx:103 611 + #: src/components/moderation/ModerationDetailsDialog.tsx:105 608 612 #: src/lib/moderation/useModerationCauseDescription.ts:98 609 613 msgid "Account Muted" 610 614 msgstr "" 611 615 612 - #: src/components/moderation/ModerationDetailsDialog.tsx:89 616 + #: src/components/moderation/ModerationDetailsDialog.tsx:91 613 617 msgid "Account Muted by List" 614 618 msgstr "" 615 619 ··· 654 658 655 659 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:169 656 660 #: src/components/dialogs/MutedWords.tsx:333 657 - #: src/components/dialogs/StarterPackDialog.tsx:371 658 - #: src/components/dialogs/StarterPackDialog.tsx:377 661 + #: src/components/dialogs/StarterPackDialog.tsx:370 662 + #: src/components/dialogs/StarterPackDialog.tsx:376 659 663 #: src/view/com/modals/UserAddRemoveLists.tsx:235 660 664 msgid "Add" 661 665 msgstr "" ··· 708 712 msgid "Add another account" 709 713 msgstr "" 710 714 711 - #: src/view/com/composer/Composer.tsx:811 715 + #: src/view/com/composer/Composer.tsx:853 712 716 msgid "Add another post" 713 717 msgstr "" 714 718 715 - #: src/view/com/composer/Composer.tsx:1444 719 + #: src/view/com/composer/Composer.tsx:1490 716 720 msgid "Add another post to thread" 717 721 msgstr "" 718 722 ··· 735 739 msgid "Add media to post" 736 740 msgstr "" 737 741 738 - #: src/components/moderation/ReportDialog/index.tsx:403 739 - #: src/components/moderation/ReportDialog/index.tsx:407 742 + #: src/components/moderation/ReportDialog/index.tsx:406 743 + #: src/components/moderation/ReportDialog/index.tsx:410 740 744 msgid "Add more details (optional)" 741 745 msgstr "" 742 746 ··· 790 794 msgid "Add to saved posts" 791 795 msgstr "" 792 796 793 - #: src/components/dialogs/StarterPackDialog.tsx:176 797 + #: src/components/dialogs/StarterPackDialog.tsx:175 794 798 #: src/view/com/profile/ProfileMenu.tsx:308 795 799 #: src/view/com/profile/ProfileMenu.tsx:311 796 800 msgid "Add to starter packs" ··· 805 809 msgid "Added to list" 806 810 msgstr "" 807 811 808 - #: src/components/dialogs/StarterPackDialog.tsx:258 812 + #: src/components/dialogs/StarterPackDialog.tsx:257 809 813 msgid "Added to starter pack" 810 814 msgstr "" 811 815 ··· 813 817 msgid "Additional details (limit 1000 characters)" 814 818 msgstr "" 815 819 816 - #: src/components/moderation/ReportDialog/index.tsx:421 820 + #: src/components/moderation/ReportDialog/index.tsx:424 817 821 msgid "Additional details (limit 300 characters)" 818 822 msgstr "" 819 823 ··· 879 883 #: src/screens/Search/components/SearchLanguageDropdown.tsx:64 880 884 #: src/screens/Search/components/SearchLanguageDropdown.tsx:99 881 885 #: src/screens/Search/components/SearchLanguageDropdown.tsx:101 882 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:216 886 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:238 883 887 msgid "All languages" 884 888 msgstr "" 885 889 ··· 903 907 msgstr "" 904 908 905 909 #: src/screens/Settings/ActivityPrivacySettings.tsx:52 906 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:92 910 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:91 907 911 msgid "Allow others to be notified of your posts" 908 912 msgstr "" 909 913 ··· 967 971 msgstr "" 968 972 969 973 #: src/components/dialogs/GifSelect.tsx:253 970 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:298 974 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:320 971 975 msgid "An error has occurred" 972 976 msgstr "" 973 977 ··· 1012 1016 msgid "An error occurred while uploading the video." 1013 1017 msgstr "" 1014 1018 1015 - #: src/screens/Onboarding/StepFinished.tsx:359 1019 + #: src/screens/Onboarding/StepFinished.tsx:358 1016 1020 msgid "An illustration of several Bluesky posts alongside repost, like, and comment icons" 1017 1021 msgstr "" 1018 1022 ··· 1051 1055 msgid "an unknown error occurred" 1052 1056 msgstr "" 1053 1057 1054 - #: src/components/moderation/ModerationDetailsDialog.tsx:134 1058 + #: src/components/moderation/ModerationDetailsDialog.tsx:136 1055 1059 #: src/lib/moderation/useModerationCauseDescription.ts:144 1056 1060 msgid "an unknown labeler" 1057 1061 msgstr "" ··· 1089 1093 1090 1094 #: src/screens/Settings/ActivityPrivacySettings.tsx:111 1091 1095 #: src/screens/Settings/ActivityPrivacySettings.tsx:116 1092 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:163 1096 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:162 1093 1097 msgid "Anyone who follows me" 1094 1098 msgstr "" 1095 1099 ··· 1125 1129 msgid "App password names must be at least 4 characters long" 1126 1130 msgstr "" 1127 1131 1128 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:72 1129 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:75 1132 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:71 1133 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:74 1130 1134 msgid "App passwords" 1131 1135 msgstr "" 1132 1136 ··· 1135 1139 msgid "App Passwords" 1136 1140 msgstr "" 1137 1141 1138 - #: src/components/moderation/LabelsOnMeDialog.tsx:150 1139 - #: src/components/moderation/LabelsOnMeDialog.tsx:153 1142 + #: src/components/moderation/LabelsOnMeDialog.tsx:152 1143 + #: src/components/moderation/LabelsOnMeDialog.tsx:155 1140 1144 msgid "Appeal" 1141 1145 msgstr "" 1142 1146 1143 - #: src/components/moderation/LabelsOnMeDialog.tsx:268 1147 + #: src/components/moderation/LabelsOnMeDialog.tsx:270 1144 1148 msgid "Appeal \"{0}\" label" 1145 1149 msgstr "" 1146 1150 1147 - #: src/components/moderation/LabelsOnMeDialog.tsx:258 1151 + #: src/components/moderation/LabelsOnMeDialog.tsx:260 1148 1152 #: src/screens/Messages/components/ChatDisabled.tsx:103 1149 1153 msgctxt "toast" 1150 1154 msgid "Appeal submitted" ··· 1221 1225 msgid "Are you sure you want to remove this from your feeds?" 1222 1226 msgstr "" 1223 1227 1224 - #: src/view/com/composer/Composer.tsx:760 1228 + #: src/view/com/composer/Composer.tsx:802 1225 1229 msgid "Are you sure you'd like to discard this draft?" 1226 1230 msgstr "" 1227 1231 1228 - #: src/view/com/composer/Composer.tsx:950 1232 + #: src/view/com/composer/Composer.tsx:992 1229 1233 msgid "Are you sure you'd like to discard this post?" 1230 1234 msgstr "" 1231 1235 ··· 1233 1237 msgid "Are you sure?" 1234 1238 msgstr "" 1235 1239 1236 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:89 1240 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:99 1237 1241 msgid "Are you writing in <0>{suggestedLanguageName}</0>?" 1238 1242 msgstr "" 1239 1243 ··· 1274 1278 msgid "Available" 1275 1279 msgstr "" 1276 1280 1277 - #: src/components/moderation/LabelsOnMeDialog.tsx:315 1278 - #: src/components/moderation/LabelsOnMeDialog.tsx:316 1281 + #: src/components/moderation/LabelsOnMeDialog.tsx:317 1282 + #: src/components/moderation/LabelsOnMeDialog.tsx:318 1279 1283 #: src/screens/Login/ChooseAccountForm.tsx:90 1280 1284 #: src/screens/Login/ChooseAccountForm.tsx:95 1281 1285 #: src/screens/Login/ForgotPasswordForm.tsx:123 ··· 1307 1311 msgid "Before creating a post or replying, you must first verify your email." 1308 1312 msgstr "" 1309 1313 1310 - #: src/components/dialogs/StarterPackDialog.tsx:71 1314 + #: src/components/dialogs/StarterPackDialog.tsx:70 1311 1315 #: src/components/StarterPack/ProfileStarterPacks.tsx:231 1312 1316 #: src/components/StarterPack/ProfileStarterPacks.tsx:241 1313 1317 msgid "Before creating a starter pack, you must first verify your email." ··· 1558 1562 msgstr "" 1559 1563 1560 1564 #: src/components/LabelingServiceCard/index.tsx:62 1561 - #: src/components/moderation/ReportDialog/index.tsx:683 1565 + #: src/components/moderation/ReportDialog/index.tsx:686 1562 1566 #: src/screens/Search/components/StarterPackCard.tsx:106 1563 1567 #: src/screens/Search/Explore.tsx:930 1564 1568 msgid "By {0}" ··· 1626 1630 #: src/screens/Settings/Settings.tsx:289 1627 1631 #: src/screens/Takendown.tsx:108 1628 1632 #: src/screens/Takendown.tsx:111 1629 - #: src/view/com/composer/Composer.tsx:1005 1630 - #: src/view/com/composer/Composer.tsx:1016 1633 + #: src/view/com/composer/Composer.tsx:1047 1634 + #: src/view/com/composer/Composer.tsx:1058 1631 1635 #: src/view/com/composer/photos/EditImageDialog.web.tsx:43 1632 1636 #: src/view/com/composer/photos/EditImageDialog.web.tsx:52 1633 1637 #: src/view/shell/desktop/LeftNav.tsx:213 ··· 1695 1699 msgid "Change Handle" 1696 1700 msgstr "" 1697 1701 1698 - #: src/components/moderation/ReportDialog/index.tsx:325 1702 + #: src/components/moderation/ReportDialog/index.tsx:328 1699 1703 msgid "Change moderation service" 1700 1704 msgstr "" 1701 1705 ··· 1708 1712 msgid "Change password dialog" 1709 1713 msgstr "" 1710 1714 1711 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:100 1712 - msgid "Change post language to {suggestedLanguageName}" 1713 - msgstr "" 1714 - 1715 - #: src/components/moderation/ReportDialog/index.tsx:244 1715 + #: src/components/moderation/ReportDialog/index.tsx:247 1716 1716 msgid "Change report reason" 1717 1717 msgstr "" 1718 1718 ··· 1823 1823 msgid "Choose People" 1824 1824 msgstr "" 1825 1825 1826 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:162 1826 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:184 1827 1827 msgid "Choose Post Languages" 1828 1828 msgstr "" 1829 1829 1830 - #: src/screens/Onboarding/StepFinished.tsx:575 1830 + #: src/screens/Onboarding/StepFinished.tsx:573 1831 1831 msgid "Choose the algorithms that power your custom feeds." 1832 1832 msgstr "" 1833 1833 ··· 1920 1920 #: src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx:178 1921 1921 #: src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx:187 1922 1922 #: src/components/dialogs/SearchablePeopleList.tsx:295 1923 - #: src/components/dialogs/StarterPackDialog.tsx:179 1923 + #: src/components/dialogs/StarterPackDialog.tsx:178 1924 1924 #: src/components/dms/EmojiPopup.android.tsx:58 1925 1925 #: src/components/dms/ReportDialog.tsx:387 1926 1926 #: src/components/dms/ReportDialog.tsx:396 ··· 1938 1938 #: src/components/WhoCanReply.tsx:209 1939 1939 #: src/screens/Settings/components/ChangePasswordDialog.tsx:286 1940 1940 #: src/screens/Settings/components/ChangePasswordDialog.tsx:291 1941 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:313 1941 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:335 1942 1942 #: src/view/com/feeds/MissingFeed.tsx:210 1943 1943 #: src/view/com/feeds/MissingFeed.tsx:217 1944 1944 msgid "Close" ··· 1962 1962 #: src/components/dialogs/GifSelect.tsx:263 1963 1963 #: src/components/verification/VerificationsDialog.tsx:136 1964 1964 #: src/components/verification/VerifierDialog.tsx:142 1965 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:182 1966 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:276 1967 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:308 1965 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:204 1966 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:298 1967 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:330 1968 1968 msgid "Close dialog" 1969 1969 msgstr "" 1970 1970 ··· 2007 2007 msgid "Closes password update alert" 2008 2008 msgstr "" 2009 2009 2010 - #: src/view/com/composer/Composer.tsx:1013 2010 + #: src/view/com/composer/Composer.tsx:1055 2011 2011 msgid "Closes post composer and discards post draft" 2012 2012 msgstr "" 2013 2013 ··· 2053 2053 msgid "Community Guidelines" 2054 2054 msgstr "" 2055 2055 2056 - #: src/screens/Onboarding/StepFinished.tsx:473 2057 - #: src/screens/Onboarding/StepFinished.tsx:588 2056 + #: src/screens/Onboarding/StepFinished.tsx:472 2057 + #: src/screens/Onboarding/StepFinished.tsx:586 2058 2058 msgid "Complete onboarding and start using your account" 2059 2059 msgstr "" 2060 2060 ··· 2066 2066 msgid "Compose new post" 2067 2067 msgstr "" 2068 2068 2069 - #: src/view/com/composer/Composer.tsx:914 2069 + #: src/view/com/composer/Composer.tsx:956 2070 2070 msgid "Compose posts up to {0, plural, other {# characters}} in length" 2071 2071 msgstr "" 2072 2072 ··· 2074 2074 msgid "Compose reply" 2075 2075 msgstr "" 2076 2076 2077 - #: src/view/com/composer/Composer.tsx:1834 2077 + #: src/view/com/composer/Composer.tsx:1883 2078 2078 msgid "Compressing video..." 2079 2079 msgstr "" 2080 2080 ··· 2186 2186 msgid "Content Languages" 2187 2187 msgstr "" 2188 2188 2189 - #: src/components/moderation/ModerationDetailsDialog.tsx:82 2189 + #: src/components/moderation/ModerationDetailsDialog.tsx:84 2190 2190 #: src/lib/moderation/useModerationCauseDescription.ts:82 2191 2191 msgid "Content Not Available" 2192 2192 msgstr "" 2193 2193 2194 - #: src/components/moderation/ModerationDetailsDialog.tsx:50 2194 + #: src/components/moderation/ModerationDetailsDialog.tsx:52 2195 2195 #: src/components/moderation/ScreenHider.tsx:99 2196 2196 #: src/lib/moderation/useGlobalLabelStrings.ts:22 2197 2197 #: src/lib/moderation/useModerationCauseDescription.ts:45 ··· 2411 2411 2412 2412 #. Text on button to create a new starter pack 2413 2413 #. Text on button to create a new starter pack 2414 - #: src/components/dialogs/StarterPackDialog.tsx:112 2415 - #: src/components/dialogs/StarterPackDialog.tsx:201 2414 + #: src/components/dialogs/StarterPackDialog.tsx:111 2415 + #: src/components/dialogs/StarterPackDialog.tsx:200 2416 2416 #: src/components/StarterPack/ProfileStarterPacks.tsx:296 2417 2417 msgid "Create" 2418 2418 msgstr "" ··· 2478 2478 msgid "Create new account" 2479 2479 msgstr "" 2480 2480 2481 - #: src/components/moderation/ReportDialog/index.tsx:585 2481 + #: src/components/moderation/ReportDialog/index.tsx:588 2482 2482 #: src/components/ReportDialog/SelectReportOptionView.tsx:102 2483 2483 msgid "Create report for {0}" 2484 2484 msgstr "" 2485 2485 2486 - #: src/components/dialogs/StarterPackDialog.tsx:107 2487 - #: src/components/dialogs/StarterPackDialog.tsx:196 2486 + #: src/components/dialogs/StarterPackDialog.tsx:106 2487 + #: src/components/dialogs/StarterPackDialog.tsx:195 2488 2488 msgid "Create starter pack" 2489 2489 msgstr "" 2490 2490 ··· 2639 2639 2640 2640 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:685 2641 2641 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:687 2642 - #: src/view/com/composer/Composer.tsx:924 2642 + #: src/view/com/composer/Composer.tsx:966 2643 2643 msgid "Delete post" 2644 2644 msgstr "" 2645 2645 ··· 2752 2752 2753 2753 #: src/components/dialogs/lists/CreateOrEditListDialog.tsx:92 2754 2754 #: src/screens/Profile/Header/EditProfileDialog.tsx:82 2755 - #: src/view/com/composer/Composer.tsx:762 2756 - #: src/view/com/composer/Composer.tsx:957 2755 + #: src/view/com/composer/Composer.tsx:804 2756 + #: src/view/com/composer/Composer.tsx:999 2757 2757 msgid "Discard" 2758 2758 msgstr "" 2759 2759 ··· 2762 2762 msgid "Discard changes?" 2763 2763 msgstr "" 2764 2764 2765 - #: src/view/com/composer/Composer.tsx:759 2765 + #: src/view/com/composer/Composer.tsx:801 2766 2766 msgid "Discard draft?" 2767 2767 msgstr "" 2768 2768 2769 - #: src/view/com/composer/Composer.tsx:949 2769 + #: src/view/com/composer/Composer.tsx:991 2770 2770 msgid "Discard post?" 2771 2771 msgstr "" 2772 2772 ··· 2789 2789 msgid "Dismiss" 2790 2790 msgstr "" 2791 2791 2792 - #: src/view/com/composer/Composer.tsx:1758 2792 + #: src/view/com/composer/Composer.tsx:1807 2793 2793 msgid "Dismiss error" 2794 2794 msgstr "" 2795 2795 ··· 2815 2815 msgid "Display name" 2816 2816 msgstr "" 2817 2817 2818 - #: src/screens/Onboarding/StepFinished.tsx:347 2818 + #: src/screens/Onboarding/StepFinished.tsx:346 2819 2819 msgid "Ditch the trolls and clickbait. Find real people and conversations that matter to you." 2820 2820 msgstr "" 2821 2821 ··· 2868 2868 #: src/view/com/auth/server-input/index.tsx:233 2869 2869 #: src/view/com/composer/labels/LabelsBtn.tsx:223 2870 2870 #: src/view/com/composer/labels/LabelsBtn.tsx:230 2871 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:281 2871 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:303 2872 2872 #: src/view/com/composer/videos/SubtitleDialog.tsx:168 2873 2873 #: src/view/com/composer/videos/SubtitleDialog.tsx:178 2874 2874 msgid "Done" ··· 3055 3055 msgid "Email 2FA disabled" 3056 3056 msgstr "" 3057 3057 3058 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:63 3058 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:62 3059 3059 msgid "Email 2FA enabled" 3060 3060 msgstr "" 3061 3061 ··· 3126 3126 msgstr "" 3127 3127 3128 3128 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:139 3129 + #: src/view/screens/Storybook/Admonitions.tsx:75 3129 3130 msgid "Enable notifications for an account by visiting their profile and pressing the <0>bell icon</0> <1/>." 3130 3131 msgstr "" 3131 3132 ··· 3222 3223 msgid "Entertainment" 3223 3224 msgstr "" 3224 3225 3225 - #: src/view/com/composer/Composer.tsx:1843 3226 + #: src/view/com/composer/Composer.tsx:1892 3226 3227 #: src/view/com/util/error/ErrorScreen.tsx:42 3227 3228 msgid "Error" 3228 3229 msgstr "" ··· 3231 3232 msgid "Error loading post" 3232 3233 msgstr "" 3233 3234 3234 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:154 3235 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:153 3235 3236 msgid "Error loading preference" 3236 3237 msgstr "" 3237 3238 ··· 3345 3346 msgid "Expires {0}" 3346 3347 msgstr "" 3347 3348 3348 - #: src/components/moderation/LabelsOnMeDialog.tsx:199 3349 - #: src/components/moderation/ModerationDetailsDialog.tsx:208 3349 + #: src/components/moderation/LabelsOnMeDialog.tsx:201 3350 + #: src/components/moderation/ModerationDetailsDialog.tsx:210 3350 3351 msgid "Expires in {0}" 3351 3352 msgstr "" 3352 3353 ··· 3414 3415 msgid "Failed to add emoji reaction" 3415 3416 msgstr "" 3416 3417 3417 - #: src/components/dialogs/StarterPackDialog.tsx:270 3418 + #: src/components/dialogs/StarterPackDialog.tsx:269 3418 3419 msgid "Failed to add to starter pack" 3419 3420 msgstr "" 3420 3421 ··· 3520 3521 msgid "Failed to remove emoji reaction" 3521 3522 msgstr "" 3522 3523 3523 - #: src/components/dialogs/StarterPackDialog.tsx:289 3524 + #: src/components/dialogs/StarterPackDialog.tsx:288 3524 3525 msgid "Failed to remove from starter pack" 3525 3526 msgstr "" 3526 3527 ··· 3554 3555 msgid "Failed to send email, please try again." 3555 3556 msgstr "" 3556 3557 3557 - #: src/components/moderation/LabelsOnMeDialog.tsx:254 3558 + #: src/components/moderation/LabelsOnMeDialog.tsx:256 3558 3559 #: src/screens/Messages/components/ChatDisabled.tsx:99 3559 3560 msgid "Failed to submit appeal, please try again." 3560 3561 msgstr "" ··· 3697 3698 msgid "Filter who you receive notifications from" 3698 3699 msgstr "" 3699 3700 3700 - #: src/screens/Onboarding/StepFinished.tsx:479 3701 - #: src/screens/Onboarding/StepFinished.tsx:591 3701 + #: src/screens/Onboarding/StepFinished.tsx:478 3702 + #: src/screens/Onboarding/StepFinished.tsx:589 3702 3703 msgid "Finalizing" 3703 3704 msgstr "" 3704 3705 ··· 3718 3719 msgid "Find people to follow" 3719 3720 msgstr "" 3720 3721 3721 - #: src/screens/Search/Shell.tsx:476 3722 + #: src/screens/Search/Shell.tsx:475 3722 3723 msgid "Find posts, users, and feeds on Bluesky" 3723 3724 msgstr "" 3724 3725 3725 - #: src/screens/Onboarding/StepFinished.tsx:345 3726 + #: src/screens/Onboarding/StepFinished.tsx:344 3726 3727 msgid "Find your people" 3727 3728 msgstr "" 3728 3729 ··· 3749 3750 msgid "Flat White" 3750 3751 msgstr "" 3751 3752 3752 - #: src/screens/Onboarding/StepFinished.tsx:571 3753 + #: src/screens/Onboarding/StepFinished.tsx:569 3753 3754 msgid "Flexible" 3754 3755 msgstr "" 3755 3756 ··· 3923 3924 msgid "Forever" 3924 3925 msgstr "" 3925 3926 3926 - #: src/screens/Onboarding/StepFinished.tsx:354 3927 + #: src/screens/Onboarding/StepFinished.tsx:353 3927 3928 msgid "Forget the noise" 3928 3929 msgstr "" 3929 3930 ··· 3940 3941 msgid "Forgot?" 3941 3942 msgstr "" 3942 3943 3943 - #: src/screens/Onboarding/StepFinished.tsx:336 3944 + #: src/screens/Onboarding/StepFinished.tsx:335 3944 3945 msgid "Free your feed" 3945 3946 msgstr "" 3946 3947 ··· 4418 4419 msgstr "" 4419 4420 4420 4421 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:157 4422 + #: src/view/screens/Storybook/Admonitions.tsx:89 4421 4423 msgid "If you want to restrict who can receive notifications for your account's activity, you can change this in <0>Settings โ†’ Privacy and Security</0>." 4422 4424 msgstr "" 4423 4425 ··· 4596 4598 msgid "It's just you right now! Add more people to your starter pack by searching above." 4597 4599 msgstr "" 4598 4600 4599 - #: src/view/com/composer/Composer.tsx:1777 4601 + #: src/view/com/composer/Composer.tsx:1826 4600 4602 msgid "Job ID: {0}" 4601 4603 msgstr "" 4602 4604 ··· 4660 4662 msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network." 4661 4663 msgstr "" 4662 4664 4663 - #: src/components/moderation/LabelsOnMeDialog.tsx:72 4665 + #: src/components/moderation/LabelsOnMeDialog.tsx:74 4664 4666 msgid "Labels on your account" 4665 4667 msgstr "" 4666 4668 4667 - #: src/components/moderation/LabelsOnMeDialog.tsx:74 4669 + #: src/components/moderation/LabelsOnMeDialog.tsx:76 4668 4670 msgid "Labels on your content" 4669 4671 msgstr "" 4670 4672 ··· 4746 4748 msgid "Learn more about verification on Bluesky" 4747 4749 msgstr "" 4748 4750 4749 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:128 4750 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:131 4751 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:127 4752 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:130 4751 4753 msgid "Learn more about what is public on Bluesky." 4752 4754 msgstr "" 4753 4755 ··· 4799 4801 msgid "Let's get your password reset!" 4800 4802 msgstr "" 4801 4803 4802 - #: src/screens/Onboarding/StepFinished.tsx:481 4803 - #: src/screens/Onboarding/StepFinished.tsx:591 4804 + #: src/screens/Onboarding/StepFinished.tsx:480 4805 + #: src/screens/Onboarding/StepFinished.tsx:589 4804 4806 msgid "Let's go!" 4805 4807 msgstr "" 4806 4808 ··· 4852 4854 4853 4855 #: src/screens/Post/PostLikedBy.tsx:41 4854 4856 #: src/screens/Profile/ProfileLabelerLikedBy.tsx:32 4855 - #: src/view/screens/ProfileFeedLikedBy.tsx:34 4857 + #: src/view/screens/ProfileFeedLikedBy.tsx:32 4856 4858 msgid "Liked By" 4857 4859 msgstr "" 4858 4860 ··· 5031 5033 msgid "Log" 5032 5034 msgstr "" 5033 5035 5034 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:107 5036 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:106 5035 5037 msgid "Logged-out visibility" 5036 5038 msgstr "" 5037 5039 ··· 5205 5207 msgid "Moderation" 5206 5208 msgstr "" 5207 5209 5208 - #: src/components/moderation/ModerationDetailsDialog.tsx:138 5210 + #: src/components/moderation/ModerationDetailsDialog.tsx:140 5209 5211 msgid "Moderation details" 5210 5212 msgstr "" 5211 5213 ··· 5254 5256 msgid "Moderation tools" 5255 5257 msgstr "" 5256 5258 5257 - #: src/components/moderation/ModerationDetailsDialog.tsx:52 5259 + #: src/components/moderation/ModerationDetailsDialog.tsx:54 5258 5260 #: src/lib/moderation/useModerationCauseDescription.ts:47 5259 5261 msgid "Moderator has chosen to set a general warning on the content." 5260 5262 msgstr "" ··· 5264 5266 msgid "More feeds" 5265 5267 msgstr "" 5266 5268 5267 - #: src/view/com/composer/select-language/PostLanguageSelect.tsx:70 5268 - #: src/view/com/composer/select-language/PostLanguageSelect.tsx:73 5269 + #: src/view/com/composer/select-language/PostLanguageSelect.tsx:87 5270 + #: src/view/com/composer/select-language/PostLanguageSelect.tsx:90 5269 5271 msgid "More languages..." 5270 5272 msgstr "" 5271 5273 ··· 5370 5372 msgstr "" 5371 5373 5372 5374 #: src/Navigation.tsx:187 5373 - #: src/view/screens/ModerationMutedAccounts.tsx:118 5375 + #: src/view/screens/ModerationMutedAccounts.tsx:116 5374 5376 msgid "Muted Accounts" 5375 5377 msgstr "" 5376 5378 5377 - #: src/view/screens/ModerationMutedAccounts.tsx:204 5379 + #: src/view/screens/ModerationMutedAccounts.tsx:202 5378 5380 msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." 5379 5381 msgstr "" 5380 5382 ··· 5437 5439 msgid "Navigates to your profile" 5438 5440 msgstr "" 5439 5441 5440 - #: src/components/moderation/ReportDialog/index.tsx:271 5441 - #: src/components/moderation/ReportDialog/index.tsx:288 5442 + #: src/components/moderation/ReportDialog/index.tsx:274 5443 + #: src/components/moderation/ReportDialog/index.tsx:291 5442 5444 msgid "Need to report a copyright violation, legal request, or regulatory compliance issue?" 5443 5445 msgstr "" 5444 5446 ··· 5446 5448 msgid "Need to report a copyright violation?" 5447 5449 msgstr "" 5448 5450 5449 - #: src/screens/Onboarding/StepFinished.tsx:559 5451 + #: src/screens/Onboarding/StepFinished.tsx:557 5450 5452 msgid "Never lose access to your followers or data." 5451 5453 msgstr "" 5452 5454 ··· 5546 5548 msgid "New posts from {firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}}" 5547 5549 msgstr "" 5548 5550 5549 - #: src/components/dialogs/StarterPackDialog.tsx:193 5551 + #: src/components/dialogs/StarterPackDialog.tsx:192 5550 5552 msgid "New starter pack" 5551 5553 msgstr "" 5552 5554 ··· 5573 5575 #: src/screens/Login/LoginForm.tsx:350 5574 5576 #: src/screens/Login/SetNewPasswordForm.tsx:182 5575 5577 #: src/screens/Login/SetNewPasswordForm.tsx:188 5576 - #: src/screens/Onboarding/StepFinished.tsx:474 5577 - #: src/screens/Onboarding/StepFinished.tsx:483 5578 + #: src/screens/Onboarding/StepFinished.tsx:473 5579 + #: src/screens/Onboarding/StepFinished.tsx:482 5578 5580 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:157 5579 5581 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:165 5580 5582 #: src/screens/Signup/BackNextButtons.tsx:67 ··· 5594 5596 msgid "Next image" 5595 5597 msgstr "" 5596 5598 5597 - #: src/screens/Onboarding/StepFinished.tsx:356 5599 + #: src/screens/Onboarding/StepFinished.tsx:355 5598 5600 msgid "No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention." 5599 5601 msgstr "" 5600 5602 ··· 5637 5639 msgid "No messages yet" 5638 5640 msgstr "" 5639 5641 5640 - #: src/screens/Onboarding/StepFinished.tsx:338 5642 + #: src/screens/Onboarding/StepFinished.tsx:337 5641 5643 msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you." 5642 5644 msgstr "" 5643 5645 ··· 5649 5651 #: src/screens/Messages/Settings.tsx:109 5650 5652 #: src/screens/Settings/ActivityPrivacySettings.tsx:129 5651 5653 #: src/screens/Settings/ActivityPrivacySettings.tsx:134 5652 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:160 5654 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:159 5653 5655 msgid "No one" 5654 5656 msgstr "" 5655 5657 ··· 5760 5762 msgid "Note about sharing" 5761 5763 msgstr "" 5762 5764 5763 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:117 5765 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:116 5764 5766 msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites." 5765 5767 msgstr "" 5766 5768 ··· 5841 5843 msgstr "" 5842 5844 5843 5845 #: src/components/dialogs/GifSelect.tsx:256 5844 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:301 5846 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:323 5845 5847 #: src/view/com/util/ErrorBoundary.tsx:57 5846 5848 msgid "Oh no!" 5847 5849 msgstr "" ··· 5878 5880 msgid "Onboarding reset" 5879 5881 msgstr "" 5880 5882 5881 - #: src/view/com/composer/Composer.tsx:359 5883 + #: src/view/com/composer/Composer.tsx:398 5882 5884 msgid "One or more GIFs is missing alt text." 5883 5885 msgstr "" 5884 5886 5885 - #: src/view/com/composer/Composer.tsx:356 5887 + #: src/view/com/composer/Composer.tsx:395 5886 5888 msgid "One or more images is missing alt text." 5887 5889 msgstr "" 5888 5890 ··· 5894 5896 msgid "One or more of your selected files are too large. Maximum size is 100ย MB." 5895 5897 msgstr "" 5896 5898 5897 - #: src/view/com/composer/Composer.tsx:366 5899 + #: src/view/com/composer/Composer.tsx:405 5898 5900 msgid "One or more videos is missing alt text." 5899 5901 msgstr "" 5900 5902 ··· 5908 5910 5909 5911 #: src/screens/Settings/ActivityPrivacySettings.tsx:120 5910 5912 #: src/screens/Settings/ActivityPrivacySettings.tsx:125 5911 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:158 5913 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:157 5912 5914 msgid "Only followers who I follow" 5913 5915 msgstr "" 5914 5916 ··· 5933 5935 msgid "Oops!" 5934 5936 msgstr "" 5935 5937 5936 - #: src/screens/Onboarding/StepFinished.tsx:555 5938 + #: src/screens/Onboarding/StepFinished.tsx:553 5937 5939 msgid "Open" 5938 5940 msgstr "" 5939 5941 ··· 5951 5953 msgstr "" 5952 5954 5953 5955 #: src/screens/Messages/components/MessageInput.web.tsx:181 5954 - #: src/view/com/composer/Composer.tsx:1429 5956 + #: src/view/com/composer/Composer.tsx:1475 5955 5957 msgid "Open emoji picker" 5956 5958 msgstr "" 5957 5959 ··· 6051 6053 msgid "Opens device gallery to select up to {MAX_IMAGES, plural, other {# images}}, or a single video or GIF." 6052 6054 msgstr "" 6053 6055 6054 - #: src/view/com/composer/Composer.tsx:1430 6056 + #: src/view/com/composer/Composer.tsx:1476 6055 6057 msgid "Opens emoji picker" 6056 6058 msgstr "" 6057 6059 ··· 6085 6087 msgid "Opens password reset form" 6086 6088 msgstr "" 6087 6089 6088 - #: src/view/com/composer/select-language/PostLanguageSelect.tsx:103 6090 + #: src/view/com/composer/select-language/PostLanguageSelect.tsx:129 6089 6091 msgid "Opens post language settings" 6090 6092 msgstr "" 6091 6093 ··· 6411 6413 msgid "Please enter your username" 6412 6414 msgstr "" 6413 6415 6414 - #: src/components/moderation/LabelsOnMeDialog.tsx:290 6416 + #: src/components/moderation/LabelsOnMeDialog.tsx:292 6415 6417 msgid "Please explain why you think this label was incorrectly applied by {0}" 6416 6418 msgstr "" 6417 6419 ··· 6463 6465 msgid "Porn" 6464 6466 msgstr "" 6465 6467 6466 - #: src/screens/PostThread/index.tsx:502 6468 + #: src/screens/PostThread/index.tsx:503 6467 6469 msgctxt "description" 6468 6470 msgid "Post" 6469 6471 msgstr "" 6470 6472 6471 - #: src/view/com/composer/Composer.tsx:1076 6473 + #: src/view/com/composer/Composer.tsx:1118 6472 6474 msgctxt "action" 6473 6475 msgid "Post" 6474 6476 msgstr "" 6475 6477 6476 - #: src/view/com/composer/Composer.tsx:1074 6478 + #: src/view/com/composer/Composer.tsx:1116 6477 6479 msgctxt "action" 6478 6480 msgid "Post All" 6479 6481 msgstr "" ··· 6505 6507 msgid "Post has been deleted" 6506 6508 msgstr "" 6507 6509 6508 - #: src/components/moderation/ModerationDetailsDialog.tsx:107 6510 + #: src/components/moderation/ModerationDetailsDialog.tsx:109 6509 6511 #: src/lib/moderation/useModerationCauseDescription.ts:106 6510 6512 msgid "Post Hidden by Muted Word" 6511 6513 msgstr "" 6512 6514 6513 - #: src/components/moderation/ModerationDetailsDialog.tsx:110 6515 + #: src/components/moderation/ModerationDetailsDialog.tsx:112 6514 6516 #: src/lib/moderation/useModerationCauseDescription.ts:115 6515 6517 msgid "Post Hidden by You" 6516 6518 msgstr "" ··· 6525 6527 msgstr "" 6526 6528 6527 6529 #. Accessibility label for button that opens dialog to choose post language settings 6528 - #: src/view/com/composer/select-language/PostLanguageSelect.tsx:98 6530 + #: src/view/com/composer/select-language/PostLanguageSelect.tsx:124 6529 6531 msgid "Post language selection" 6530 6532 msgstr "" 6531 6533 ··· 6618 6620 #: src/Navigation.tsx:407 6619 6621 #: src/Navigation.tsx:415 6620 6622 #: src/screens/Settings/ActivityPrivacySettings.tsx:40 6621 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:45 6623 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:44 6622 6624 msgid "Privacy and Security" 6623 6625 msgstr "" 6624 6626 6625 6627 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:161 6628 + #: src/view/screens/Storybook/Admonitions.tsx:93 6626 6629 msgid "Privacy and Security settings" 6627 6630 msgstr "" 6628 6631 ··· 6637 6640 msgid "Privacy Policy" 6638 6641 msgstr "" 6639 6642 6640 - #: src/view/com/composer/Composer.tsx:1840 6643 + #: src/view/com/composer/Composer.tsx:1889 6641 6644 msgid "Processing video..." 6642 6645 msgstr "" 6643 6646 ··· 6663 6666 msgid "Profile updated" 6664 6667 msgstr "" 6665 6668 6666 - #: src/screens/Onboarding/StepFinished.tsx:541 6669 + #: src/screens/Onboarding/StepFinished.tsx:539 6667 6670 msgid "Public" 6668 6671 msgstr "" 6669 6672 ··· 6676 6679 msgstr "" 6677 6680 6678 6681 #. Accessibility label for button to publish a single post 6679 - #: src/view/com/composer/Composer.tsx:1056 6682 + #: src/view/com/composer/Composer.tsx:1098 6680 6683 msgid "Publish post" 6681 6684 msgstr "" 6682 6685 6683 6686 #. Accessibility label for button to publish multiple posts in a thread 6684 - #: src/view/com/composer/Composer.tsx:1049 6687 + #: src/view/com/composer/Composer.tsx:1091 6685 6688 msgid "Publish posts" 6686 6689 msgstr "" 6687 6690 6688 6691 #. Accessibility label for button to publish multiple replies in a thread 6689 - #: src/view/com/composer/Composer.tsx:1034 6692 + #: src/view/com/composer/Composer.tsx:1076 6690 6693 msgid "Publish replies" 6691 6694 msgstr "" 6692 6695 6693 6696 #. Accessibility label for button to publish a single reply 6694 - #: src/view/com/composer/Composer.tsx:1041 6697 + #: src/view/com/composer/Composer.tsx:1083 6695 6698 msgid "Publish reply" 6696 6699 msgstr "" 6697 6700 ··· 6850 6853 msgid "Recent Searches" 6851 6854 msgstr "" 6852 6855 6853 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:210 6856 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:232 6854 6857 msgid "Recently used" 6855 6858 msgstr "" 6856 6859 ··· 6878 6881 6879 6882 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:171 6880 6883 #: src/components/dialogs/MutedWords.tsx:443 6881 - #: src/components/dialogs/StarterPackDialog.tsx:371 6882 - #: src/components/dialogs/StarterPackDialog.tsx:377 6884 + #: src/components/dialogs/StarterPackDialog.tsx:370 6885 + #: src/components/dialogs/StarterPackDialog.tsx:376 6883 6886 #: src/components/FeedCard.tsx:343 6884 6887 #: src/components/StarterPack/Wizard/WizardListCard.tsx:104 6885 6888 #: src/components/StarterPack/Wizard/WizardListCard.tsx:111 ··· 7024 7027 msgid "Removed from saved posts" 7025 7028 msgstr "" 7026 7029 7027 - #: src/components/dialogs/StarterPackDialog.tsx:277 7030 + #: src/components/dialogs/StarterPackDialog.tsx:276 7028 7031 msgid "Removed from starter pack" 7029 7032 msgstr "" 7030 7033 ··· 7081 7084 msgid "Replies to this post are disabled." 7082 7085 msgstr "" 7083 7086 7084 - #: src/view/com/composer/Composer.tsx:1072 7087 + #: src/view/com/composer/Composer.tsx:1114 7085 7088 msgctxt "action" 7086 7089 msgid "Reply" 7087 7090 msgstr "" ··· 7091 7094 msgid "Reply ({0, plural, one {# reply} other {# replies}})" 7092 7095 msgstr "" 7093 7096 7094 - #: src/components/moderation/ModerationDetailsDialog.tsx:116 7097 + #: src/components/moderation/ModerationDetailsDialog.tsx:118 7095 7098 #: src/lib/moderation/useModerationCauseDescription.ts:125 7096 7099 msgid "Reply Hidden by Thread Author" 7097 7100 msgstr "" 7098 7101 7099 - #: src/components/moderation/ModerationDetailsDialog.tsx:115 7102 + #: src/components/moderation/ModerationDetailsDialog.tsx:117 7100 7103 #: src/lib/moderation/useModerationCauseDescription.ts:124 7101 7104 msgid "Reply Hidden by You" 7102 7105 msgstr "" ··· 7345 7348 #: src/components/dms/MessageItem.tsx:322 7346 7349 #: src/components/Error.tsx:65 7347 7350 #: src/components/Lists.tsx:110 7348 - #: src/components/moderation/ReportDialog/index.tsx:229 7351 + #: src/components/moderation/ReportDialog/index.tsx:232 7349 7352 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:55 7350 7353 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:58 7351 7354 #: src/components/StarterPack/ProfileStarterPacks.tsx:342 ··· 7363 7366 #: src/screens/Signup/BackNextButtons.tsx:53 7364 7367 #: src/view/com/util/error/ErrorMessage.tsx:60 7365 7368 #: src/view/com/util/error/ErrorScreen.tsx:97 7369 + #: src/view/screens/Storybook/Admonitions.tsx:63 7366 7370 msgid "Retry" 7367 7371 msgstr "" 7368 7372 7369 - #: src/components/moderation/ReportDialog/index.tsx:226 7373 + #: src/components/moderation/ReportDialog/index.tsx:229 7374 + #: src/view/screens/Storybook/Admonitions.tsx:60 7370 7375 msgid "Retry loading report options" 7371 7376 msgstr "" 7372 7377 ··· 7496 7501 #: src/components/forms/SearchInput.tsx:34 7497 7502 #: src/components/forms/SearchInput.tsx:36 7498 7503 #: src/screens/Search/Shell.tsx:307 7499 - #: src/screens/Search/Shell.tsx:464 7504 + #: src/screens/Search/Shell.tsx:463 7500 7505 #: src/view/shell/bottom-bar/BottomBar.tsx:198 7501 7506 msgid "Search" 7502 7507 msgstr "" ··· 7556 7561 msgid "Search is currently unavailable when logged out" 7557 7562 msgstr "" 7558 7563 7559 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:193 7560 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:194 7564 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:215 7565 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:216 7561 7566 msgid "Search languages" 7562 7567 msgstr "" 7563 7568 ··· 7639 7644 msgid "Select {0}" 7640 7645 msgstr "" 7641 7646 7642 - #: src/view/com/composer/select-language/PostLanguageSelect.tsx:58 7647 + #: src/view/com/composer/select-language/PostLanguageSelect.tsx:72 7643 7648 msgid "Select {langName}" 7644 7649 msgstr "" 7645 7650 ··· 7705 7710 msgstr "" 7706 7711 7707 7712 #: src/screens/Settings/LanguageSettings.tsx:178 7708 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:226 7713 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:248 7709 7714 msgid "Select languages" 7710 7715 msgstr "" 7711 7716 7712 - #: src/components/moderation/ReportDialog/index.tsx:310 7717 + #: src/components/moderation/ReportDialog/index.tsx:313 7713 7718 msgid "Select moderation service" 7714 7719 msgstr "" 7715 7720 ··· 7717 7722 msgid "Select moderator" 7718 7723 msgstr "" 7719 7724 7720 - #: src/view/com/composer/select-language/PostLanguageSelect.tsx:45 7725 + #: src/view/com/composer/select-language/PostLanguageSelect.tsx:57 7721 7726 msgid "Select post language" 7722 7727 msgstr "" 7723 7728 ··· 7738 7743 msgid "Select the moderation service(s) to report to" 7739 7744 msgstr "" 7740 7745 7741 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:172 7746 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:194 7742 7747 msgid "Select up to 3 languages used in this post" 7743 7748 msgstr "" 7744 7749 ··· 7800 7805 msgid "Send message" 7801 7806 msgstr "" 7802 7807 7803 - #: src/components/PostControls/ShareMenu/RecentChats.tsx:123 7808 + #: src/components/PostControls/ShareMenu/RecentChats.tsx:122 7804 7809 msgid "Send post to {name}" 7805 7810 msgstr "" 7806 7811 ··· 7819 7824 msgid "Send report to {0}" 7820 7825 msgstr "" 7821 7826 7822 - #: src/components/moderation/ReportDialog/index.tsx:649 7827 + #: src/components/moderation/ReportDialog/index.tsx:652 7823 7828 msgid "Send report to {title}" 7824 7829 msgstr "" 7825 7830 ··· 7875 7880 msgid "Settings for activity from others" 7876 7881 msgstr "" 7877 7882 7878 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:85 7883 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:84 7879 7884 msgid "Settings for allowing others to be notified of your posts" 7880 7885 msgstr "" 7881 7886 ··· 8206 8211 msgid "Similar accounts" 8207 8212 msgstr "" 8208 8213 8209 - #: src/screens/Onboarding/StepFinished.tsx:380 8210 - #: src/screens/Onboarding/StepFinished.tsx:462 8214 + #: src/screens/Onboarding/StepFinished.tsx:379 8215 + #: src/screens/Onboarding/StepFinished.tsx:461 8211 8216 #: src/screens/Onboarding/StepInterests/index.tsx:240 8212 8217 #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:222 8213 8218 #: src/screens/StarterPack/Wizard/index.tsx:218 8214 8219 msgid "Skip" 8215 8220 msgstr "" 8216 8221 8217 - #: src/screens/Onboarding/StepFinished.tsx:373 8218 - #: src/screens/Onboarding/StepFinished.tsx:459 8222 + #: src/screens/Onboarding/StepFinished.tsx:372 8223 + #: src/screens/Onboarding/StepFinished.tsx:458 8219 8224 msgid "Skip introduction and start using your account" 8220 8225 msgstr "" 8221 8226 ··· 8270 8275 msgstr "" 8271 8276 8272 8277 #: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:138 8273 - #: src/components/moderation/ReportDialog/index.tsx:223 8278 + #: src/components/moderation/ReportDialog/index.tsx:224 8274 8279 #: src/screens/Deactivated.tsx:94 8275 8280 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:59 8281 + #: src/view/screens/Storybook/Admonitions.tsx:55 8276 8282 msgid "Something went wrong, please try again" 8277 8283 msgstr "" 8278 8284 ··· 8298 8304 msgid "Something wrong? Let us know." 8299 8305 msgstr "" 8300 8306 8301 - #: src/App.native.tsx:125 8302 - #: src/App.web.tsx:101 8307 + #: src/App.native.tsx:128 8308 + #: src/App.web.tsx:100 8303 8309 msgid "Sorry! Your session expired. Please sign in again." 8304 8310 msgstr "" 8305 8311 ··· 8315 8321 msgid "Sort replies to the same post by:" 8316 8322 msgstr "" 8317 8323 8318 - #: src/components/moderation/LabelsOnMeDialog.tsx:178 8319 - #: src/components/moderation/ModerationDetailsDialog.tsx:186 8324 + #: src/components/moderation/LabelsOnMeDialog.tsx:180 8325 + #: src/components/moderation/ModerationDetailsDialog.tsx:188 8320 8326 msgid "Source: <0>{sourceName}</0>" 8321 8327 msgstr "" 8322 8328 ··· 8338 8344 msgid "Sports" 8339 8345 msgstr "" 8340 8346 8341 - #: src/components/PostControls/ShareMenu/RecentChats.tsx:208 8347 + #: src/components/PostControls/ShareMenu/RecentChats.tsx:207 8342 8348 msgid "Start a conversation, and it will appear here." 8343 8349 msgstr "" 8344 8350 ··· 8412 8418 8413 8419 #: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:117 8414 8420 #: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:123 8415 - #: src/components/moderation/LabelsOnMeDialog.tsx:324 8416 - #: src/components/moderation/LabelsOnMeDialog.tsx:325 8421 + #: src/components/moderation/LabelsOnMeDialog.tsx:326 8422 + #: src/components/moderation/LabelsOnMeDialog.tsx:327 8417 8423 #: src/screens/Messages/components/ChatDisabled.tsx:154 8418 8424 #: src/screens/Messages/components/ChatDisabled.tsx:155 8419 8425 msgid "Submit" ··· 8427 8433 msgid "Submit Appeal" 8428 8434 msgstr "" 8429 8435 8430 - #: src/components/moderation/ReportDialog/index.tsx:387 8431 - #: src/components/moderation/ReportDialog/index.tsx:444 8432 - #: src/components/moderation/ReportDialog/index.tsx:451 8436 + #: src/components/moderation/ReportDialog/index.tsx:390 8437 + #: src/components/moderation/ReportDialog/index.tsx:447 8438 + #: src/components/moderation/ReportDialog/index.tsx:454 8433 8439 msgid "Submit report" 8434 8440 msgstr "" 8435 8441 ··· 8630 8636 msgid "Text field" 8631 8637 msgstr "" 8632 8638 8633 - #: src/components/moderation/LabelsOnMeDialog.tsx:288 8639 + #: src/components/moderation/LabelsOnMeDialog.tsx:290 8634 8640 #: src/screens/Messages/components/ChatDisabled.tsx:120 8635 8641 msgid "Text input field" 8636 8642 msgstr "" ··· 8687 8693 msgid "The app will be restarted" 8688 8694 msgstr "" 8689 8695 8690 - #: src/components/moderation/ModerationDetailsDialog.tsx:119 8696 + #: src/components/moderation/ModerationDetailsDialog.tsx:121 8691 8697 #: src/lib/moderation/useModerationCauseDescription.ts:128 8692 8698 msgid "The author of this thread has hidden this reply." 8693 8699 msgstr "" ··· 8724 8730 msgid "The feed has been replaced with Discover." 8725 8731 msgstr "" 8726 8732 8727 - #: src/components/moderation/LabelsOnMeDialog.tsx:59 8733 + #: src/components/moderation/LabelsOnMeDialog.tsx:61 8728 8734 msgid "The following labels were applied to your account." 8729 8735 msgstr "" 8730 8736 8731 - #: src/components/moderation/LabelsOnMeDialog.tsx:60 8737 + #: src/components/moderation/LabelsOnMeDialog.tsx:62 8732 8738 msgid "The following labels were applied to your content." 8733 8739 msgstr "" 8734 8740 ··· 8748 8754 msgid "The open social network." 8749 8755 msgstr "" 8750 8756 8757 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:119 8758 + msgid "The post you're replying to was marked as being written in {suggestedLanguageName} by its author. Would you like to reply in <0>{suggestedLanguageName}</0>?" 8759 + msgstr "" 8760 + 8751 8761 #: src/view/screens/PrivacyPolicy.tsx:38 8752 8762 msgid "The Privacy Policy has been moved to <0/>" 8753 8763 msgstr "" ··· 8878 8888 msgstr "" 8879 8889 8880 8890 #: src/components/dialogs/GifSelect.tsx:258 8881 - #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:303 8891 + #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:325 8882 8892 #: src/view/com/util/ErrorBoundary.tsx:59 8883 8893 msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" 8884 8894 msgstr "" ··· 8915 8925 msgid "This action can be undone at any time." 8916 8926 msgstr "" 8917 8927 8918 - #: src/components/moderation/LabelsOnMeDialog.tsx:271 8928 + #: src/components/moderation/LabelsOnMeDialog.tsx:273 8919 8929 msgid "This appeal will be sent to <0>{sourceName}</0>." 8920 8930 msgstr "" 8921 8931 ··· 8943 8953 msgid "This content is hosted by {0}. Do you want to enable external media?" 8944 8954 msgstr "" 8945 8955 8946 - #: src/components/moderation/ModerationDetailsDialog.tsx:84 8956 + #: src/components/moderation/ModerationDetailsDialog.tsx:86 8947 8957 #: src/lib/moderation/useModerationCauseDescription.ts:84 8948 8958 msgid "This content is not available because one of the users involved has blocked the other." 8949 8959 msgstr "" ··· 9007 9017 msgid "This is not a valid link" 9008 9018 msgstr "" 9009 9019 9010 - #: src/components/moderation/ModerationDetailsDialog.tsx:168 9020 + #: src/components/moderation/ModerationDetailsDialog.tsx:170 9011 9021 msgid "This label was applied by the author." 9012 9022 msgstr "" 9013 9023 9014 - #: src/components/moderation/LabelsOnMeDialog.tsx:165 9024 + #: src/components/moderation/LabelsOnMeDialog.tsx:167 9015 9025 msgid "This label was applied by you." 9016 9026 msgstr "" 9017 9027 ··· 9059 9069 msgid "This post will be hidden from feeds and threads. This cannot be undone." 9060 9070 msgstr "" 9061 9071 9062 - #: src/view/com/composer/Composer.tsx:475 9072 + #: src/view/com/composer/Composer.tsx:514 9063 9073 msgid "This post's author has disabled quote posts." 9064 9074 msgstr "" 9065 9075 ··· 9095 9105 msgid "This user has blocked you" 9096 9106 msgstr "" 9097 9107 9098 - #: src/components/moderation/ModerationDetailsDialog.tsx:79 9108 + #: src/components/moderation/ModerationDetailsDialog.tsx:81 9099 9109 #: src/lib/moderation/useModerationCauseDescription.ts:75 9100 9110 msgid "This user has blocked you. You cannot view their content." 9101 9111 msgstr "" ··· 9104 9114 msgid "This user has requested that their content only be shown to signed-in users." 9105 9115 msgstr "" 9106 9116 9107 - #: src/components/moderation/ModerationDetailsDialog.tsx:59 9117 + #: src/components/moderation/ModerationDetailsDialog.tsx:61 9108 9118 msgid "This user is included in the <0>{0}</0> list which you have blocked." 9109 9119 msgstr "" 9110 9120 9111 - #: src/components/moderation/ModerationDetailsDialog.tsx:91 9121 + #: src/components/moderation/ModerationDetailsDialog.tsx:93 9112 9122 msgid "This user is included in the <0>{0}</0> list which you have muted." 9113 9123 msgstr "" 9114 9124 ··· 9240 9250 msgid "TV" 9241 9251 msgstr "" 9242 9252 9243 - #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:65 9253 + #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:64 9244 9254 msgid "Two-factor authentication (2FA)" 9245 9255 msgstr "" 9246 9256 ··· 9365 9375 msgid "Unfortunately, Bluesky is unavailable in Mississippi right now." 9366 9376 msgstr "" 9367 9377 9368 - #: src/components/moderation/ReportDialog/index.tsx:372 9378 + #: src/components/moderation/ReportDialog/index.tsx:375 9369 9379 msgid "Unfortunately, none of your subscribed labelers supports this report type." 9370 9380 msgstr "" 9371 9381 ··· 9487 9497 msgid "Unsubscribed from list" 9488 9498 msgstr "" 9489 9499 9490 - #: src/view/com/composer/Composer.tsx:852 9500 + #: src/view/com/composer/Composer.tsx:894 9491 9501 msgid "Unsupported video type: {mimeType}" 9492 9502 msgstr "" 9493 9503 ··· 9573 9583 msgid "Uploading link thumbnail..." 9574 9584 msgstr "" 9575 9585 9576 - #: src/view/com/composer/Composer.tsx:1837 9586 + #: src/view/com/composer/Composer.tsx:1886 9577 9587 msgid "Uploading video..." 9578 9588 msgstr "" 9579 9589 ··· 9617 9627 msgid "User blocked" 9618 9628 msgstr "" 9619 9629 9620 - #: src/components/moderation/ModerationDetailsDialog.tsx:71 9630 + #: src/components/moderation/ModerationDetailsDialog.tsx:73 9621 9631 #: src/lib/moderation/useModerationCauseDescription.ts:63 9622 9632 msgid "User Blocked" 9623 9633 msgstr "" ··· 9630 9640 msgid "User blocked by list" 9631 9641 msgstr "" 9632 9642 9633 - #: src/components/moderation/ModerationDetailsDialog.tsx:57 9643 + #: src/components/moderation/ModerationDetailsDialog.tsx:59 9634 9644 msgid "User Blocked by List" 9635 9645 msgstr "" 9636 9646 ··· 9638 9648 msgid "User Blocking You" 9639 9649 msgstr "" 9640 9650 9641 - #: src/components/moderation/ModerationDetailsDialog.tsx:77 9651 + #: src/components/moderation/ModerationDetailsDialog.tsx:79 9642 9652 msgid "User Blocks You" 9643 9653 msgstr "" 9644 9654 ··· 9831 9841 msgid "Video settings" 9832 9842 msgstr "" 9833 9843 9834 - #: src/view/com/composer/Composer.tsx:1847 9844 + #: src/view/com/composer/Composer.tsx:1896 9835 9845 msgid "Video uploaded" 9836 9846 msgstr "" 9837 9847 ··· 9847 9857 msgid "Videos must be less than 3 minutes long." 9848 9858 msgstr "" 9849 9859 9850 - #: src/view/com/composer/Composer.tsx:546 9860 + #: src/view/com/composer/Composer.tsx:585 9851 9861 msgctxt "Action to view the post the user just created" 9852 9862 msgid "View" 9853 9863 msgstr "" 9854 9864 9855 - #: src/screens/Profile/Header/Shell.tsx:229 9865 + #: src/screens/Profile/Header/Shell.tsx:241 9856 9866 msgid "View {0}'s avatar" 9857 9867 msgstr "" 9858 9868 ··· 9900 9910 9901 9911 #: src/components/interstitials/TrendingVideos.tsx:198 9902 9912 #: src/components/interstitials/TrendingVideos.tsx:220 9903 - #: src/screens/Search/modules/ExploreTrendingVideos.tsx:194 9904 - #: src/screens/Search/modules/ExploreTrendingVideos.tsx:213 9913 + #: src/screens/Search/modules/ExploreTrendingVideos.tsx:193 9914 + #: src/screens/Search/modules/ExploreTrendingVideos.tsx:212 9905 9915 msgid "View more" 9906 9916 msgstr "" 9907 9917 ··· 9909 9919 msgid "View more trending videos" 9910 9920 msgstr "" 9911 9921 9912 - #: src/view/com/composer/Composer.tsx:541 9922 + #: src/view/com/composer/Composer.tsx:580 9913 9923 msgid "View post" 9914 9924 msgstr "" 9915 9925 ··· 10040 10050 msgid "We have sent another verification email to <0>{0}</0>." 10041 10051 msgstr "" 10042 10052 10043 - #: src/screens/Onboarding/StepFinished.tsx:533 10053 + #: src/screens/Onboarding/StepFinished.tsx:531 10044 10054 msgid "We hope you have a wonderful time. Remember, Bluesky is:" 10045 10055 msgstr "" 10046 10056 ··· 10137 10147 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." 10138 10148 msgstr "" 10139 10149 10140 - #: src/view/com/composer/Composer.tsx:472 10150 + #: src/view/com/composer/Composer.tsx:511 10141 10151 msgid "We're sorry! The post you are replying to has been deleted." 10142 10152 msgstr "" 10143 10153 ··· 10188 10198 10189 10199 #: src/view/com/auth/SplashScreen.tsx:51 10190 10200 #: src/view/com/auth/SplashScreen.web.tsx:103 10191 - #: src/view/com/composer/Composer.tsx:812 10201 + #: src/view/com/composer/Composer.tsx:854 10192 10202 msgid "What's up?" 10193 10203 msgstr "" 10194 10204 ··· 10219 10229 msgstr "" 10220 10230 10221 10231 #: src/components/interstitials/TrendingVideos.tsx:125 10222 - #: src/screens/Search/modules/ExploreTrendingVideos.tsx:108 10232 + #: src/screens/Search/modules/ExploreTrendingVideos.tsx:107 10223 10233 msgid "Whoops! Trending videos failed to load." 10224 10234 msgstr "" 10225 10235 ··· 10266 10276 msgid "Write a message" 10267 10277 msgstr "" 10268 10278 10269 - #: src/view/com/composer/Composer.tsx:912 10279 + #: src/view/com/composer/Composer.tsx:954 10270 10280 msgid "Write post" 10271 10281 msgstr "" 10272 10282 10273 10283 #: src/screens/PostThread/components/ThreadComposePrompt.tsx:90 10274 - #: src/view/com/composer/Composer.tsx:810 10284 + #: src/view/com/composer/Composer.tsx:852 10275 10285 msgid "Write your reply" 10276 10286 msgstr "" 10277 10287 ··· 10289 10299 msgid "www.mylivestream.tv" 10290 10300 msgstr "" 10291 10301 10292 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:102 10302 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:181 10293 10303 msgid "Yes" 10294 10304 msgstr "" 10295 10305 ··· 10456 10466 msgid "You have blocked this user" 10457 10467 msgstr "" 10458 10468 10459 - #: src/components/moderation/ModerationDetailsDialog.tsx:73 10469 + #: src/components/moderation/ModerationDetailsDialog.tsx:75 10460 10470 #: src/lib/moderation/useModerationCauseDescription.ts:57 10461 10471 #: src/lib/moderation/useModerationCauseDescription.ts:65 10462 10472 msgid "You have blocked this user. You cannot view their content." ··· 10476 10486 msgid "You have hidden this post" 10477 10487 msgstr "" 10478 10488 10479 - #: src/components/moderation/ModerationDetailsDialog.tsx:111 10489 + #: src/components/moderation/ModerationDetailsDialog.tsx:113 10480 10490 msgid "You have hidden this post." 10481 10491 msgstr "" 10482 10492 10483 - #: src/components/moderation/ModerationDetailsDialog.tsx:104 10493 + #: src/components/moderation/ModerationDetailsDialog.tsx:106 10484 10494 #: src/lib/moderation/useModerationCauseDescription.ts:99 10485 10495 msgid "You have muted this account." 10486 10496 msgstr "" ··· 10502 10512 msgid "You have no lists." 10503 10513 msgstr "" 10504 10514 10505 - #: src/components/dialogs/StarterPackDialog.tsx:101 10515 + #: src/components/dialogs/StarterPackDialog.tsx:100 10506 10516 msgid "You have no starter packs." 10507 10517 msgstr "" 10508 10518 ··· 10510 10520 msgid "You have not blocked any accounts yet. To block an account, go to their profile and select \"Block account\" from the menu on their account." 10511 10521 msgstr "" 10512 10522 10513 - #: src/view/screens/ModerationMutedAccounts.tsx:179 10523 + #: src/view/screens/ModerationMutedAccounts.tsx:177 10514 10524 msgid "You have not muted any accounts yet. To mute an account, go to their profile and select \"Mute account\" from the menu on their account." 10515 10525 msgstr "" 10516 10526 ··· 10534 10544 msgid "You haven't muted any words or tags yet" 10535 10545 msgstr "" 10536 10546 10537 - #: src/components/moderation/ModerationDetailsDialog.tsx:118 10547 + #: src/components/moderation/ModerationDetailsDialog.tsx:120 10538 10548 #: src/lib/moderation/useModerationCauseDescription.ts:127 10539 10549 msgid "You hid this reply." 10540 10550 msgstr "" ··· 10551 10561 msgid "You joined Bluesky using a starter pack {timeAgoString} ago" 10552 10562 msgstr "" 10553 10563 10554 - #: src/components/moderation/LabelsOnMeDialog.tsx:79 10564 + #: src/components/moderation/LabelsOnMeDialog.tsx:81 10555 10565 msgid "You may appeal non-self labels if you feel they were placed in error." 10556 10566 msgstr "" 10557 10567 10558 - #: src/components/moderation/LabelsOnMeDialog.tsx:84 10568 + #: src/components/moderation/LabelsOnMeDialog.tsx:86 10559 10569 msgid "You may appeal these labels if you feel they were placed in error." 10560 10570 msgstr "" 10561 10571 ··· 10685 10695 msgid "You're in line" 10686 10696 msgstr "" 10687 10697 10688 - #: src/screens/Onboarding/StepFinished.tsx:530 10698 + #: src/screens/Onboarding/StepFinished.tsx:528 10689 10699 msgid "You're ready to go!" 10690 10700 msgstr "" 10691 10701 ··· 10694 10704 msgid "You're signed in with an App Password. Please sign in with your main password to continue deactivating your account." 10695 10705 msgstr "" 10696 10706 10697 - #: src/components/moderation/ModerationDetailsDialog.tsx:108 10707 + #: src/components/moderation/ModerationDetailsDialog.tsx:110 10698 10708 #: src/lib/moderation/useModerationCauseDescription.ts:108 10699 10709 msgid "You've chosen to hide a word or tag within this post." 10700 10710 msgstr "" ··· 10849 10859 msgid "Your password must be at least 8 characters long." 10850 10860 msgstr "" 10851 10861 10852 - #: src/view/com/composer/Composer.tsx:537 10862 + #: src/view/com/composer/Composer.tsx:576 10853 10863 msgid "Your post was sent" 10854 10864 msgstr "" 10855 10865 10856 - #: src/view/com/composer/Composer.tsx:534 10866 + #: src/view/com/composer/Composer.tsx:573 10857 10867 msgid "Your posts were sent" 10858 10868 msgstr "" 10859 10869 10860 - #: src/screens/Onboarding/StepFinished.tsx:545 10870 + #: src/screens/Onboarding/StepFinished.tsx:543 10861 10871 msgid "Your posts, likes, and blocks are public. Mutes are private." 10862 10872 msgstr "" 10863 10873 ··· 10865 10875 msgid "Your preferred language" 10866 10876 msgstr "" 10867 10877 10868 - #: src/screens/Onboarding/StepFinished.tsx:422 10878 + #: src/screens/Onboarding/StepFinished.tsx:421 10869 10879 msgid "Your profile picture" 10870 10880 msgstr "" 10871 10881 10872 - #: src/screens/Onboarding/StepFinished.tsx:350 10882 + #: src/screens/Onboarding/StepFinished.tsx:349 10873 10883 msgid "Your profile picture surrounded by concentric circles of other users' profile pictures" 10874 10884 msgstr "" 10875 10885 ··· 10877 10887 msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." 10878 10888 msgstr "" 10879 10889 10880 - #: src/view/com/composer/Composer.tsx:536 10890 + #: src/view/com/composer/Composer.tsx:575 10881 10891 msgid "Your reply was sent" 10882 10892 msgstr "" 10883 10893 10884 - #: src/components/moderation/ReportDialog/index.tsx:394 10894 + #: src/components/moderation/ReportDialog/index.tsx:397 10885 10895 msgid "Your report will be sent to <0>{0}</0>." 10886 10896 msgstr "" 10887 10897
+10 -5
src/logger/metrics.ts
··· 175 175 'feed:suggestion:press': { 176 176 feedUrl: string 177 177 } 178 - 'discover:showMore': { 178 + 'feed:showMore': { 179 + feed: string 179 180 feedContext: string 180 181 } 181 - 'discover:showLess': { 182 + 'feed:showLess': { 183 + feed: string 182 184 feedContext: string 183 185 } 184 - 'discover:clickthrough': { 186 + 'feed:clickthrough': { 187 + feed: string 185 188 count: number 186 189 } 187 - 'discover:engaged': { 190 + 'feed:engaged': { 191 + feed: string 188 192 count: number 189 193 } 190 - 'discover:seen': { 194 + 'feed:seen': { 195 + feed: string 191 196 count: number 192 197 } 193 198
-2
src/screens/Onboarding/StepFinished.tsx
··· 69 69 import * as bsky from '#/types/bsky' 70 70 71 71 export function StepFinished() { 72 - const {_} = useLingui() 73 72 const {state, dispatch} = useContext(Context) 74 73 const onboardDispatch = useOnboardingDispatch() 75 74 const [saving, setSaving] = useState(false) ··· 495 494 496 495 function Dot({active}: {active: boolean}) { 497 496 const t = useTheme() 498 - const {_} = useLingui() 499 497 500 498 return ( 501 499 <View
+1 -1
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 621 621 622 622 if (!isBackdated) return null 623 623 624 - const orange = t.name === 'light' ? colors.warning.dark : colors.warning.light 624 + const orange = colors.warning 625 625 626 626 return ( 627 627 <>
+13 -12
src/screens/PostThread/index.tsx
··· 148 148 */ 149 149 const shouldHandleScroll = useRef(true) 150 150 /** 151 - * Called any time the content size of the list changes, _just_ before paint. 151 + * Called any time the content size of the list changes. Could be a fresh 152 + * render, items being added to the list, or any resize that changes the 153 + * scrollable size of the content. 152 154 * 153 155 * We want this to fire every time we change params (which will reset 154 156 * `deferParents` via `onLayout` on the anchor post, due to the key change), ··· 193 195 * will give us a _positive_ offset, which will scroll the anchor post 194 196 * back _up_ to the top of the screen. 195 197 */ 196 - list.scrollToOffset({ 197 - offset: anchorOffsetTop - headerHeight, 198 - }) 198 + const offset = anchorOffsetTop - headerHeight 199 + list.scrollToOffset({offset}) 199 200 200 201 /* 201 - * After the second pass, `deferParents` will be `false`, and we need 202 - * to ensure this doesn't run again until scroll handling is requested 203 - * again via `shouldHandleScroll.current === true` and a params 204 - * change via `prepareForParamsUpdate`. 202 + * After we manage to do a positive adjustment, we need to ensure this 203 + * doesn't run again until scroll handling is requested again via 204 + * `shouldHandleScroll.current === true` and a params change via 205 + * `prepareForParamsUpdate`. 205 206 * 206 207 * The `isRoot` here is needed because if we're looking at the anchor 207 208 * post, this handler will not fire after `deferParents` is set to 208 209 * `false`, since there are no parents to render above it. In this case, 209 - * we want to make sure `shouldHandleScroll` is set to `false` so that 210 - * subsequent size changes unrelated to a params change (like pagination) 211 - * do not affect scroll. 210 + * we want to make sure `shouldHandleScroll` is set to `false` right away 211 + * so that subsequent size changes unrelated to a params change (like 212 + * pagination) do not affect scroll. 212 213 */ 213 - if (!deferParents || isRoot) shouldHandleScroll.current = false 214 + if (offset > 0 || isRoot) shouldHandleScroll.current = false 214 215 } 215 216 }) 216 217
+23 -11
src/screens/Profile/Header/Shell.tsx
··· 209 209 210 210 {children} 211 211 212 - {!isPlaceholderProfile && ( 213 - <View 214 - style={[a.px_lg, a.pt_xs, a.pb_sm]} 215 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 216 - {isMe ? ( 217 - <LabelsOnMe type="account" labels={profile.labels} /> 218 - ) : ( 219 - <ProfileHeaderAlerts moderation={moderation} /> 220 - )} 221 - </View> 222 - )} 212 + {!isPlaceholderProfile && 213 + (isMe ? ( 214 + <LabelsOnMe 215 + type="account" 216 + labels={profile.labels} 217 + style={[ 218 + a.px_lg, 219 + a.pt_xs, 220 + a.pb_sm, 221 + isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 222 + ]} 223 + /> 224 + ) : ( 225 + <ProfileHeaderAlerts 226 + moderation={moderation} 227 + style={[ 228 + a.px_lg, 229 + a.pt_xs, 230 + a.pb_sm, 231 + isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 232 + ]} 233 + /> 234 + ))} 223 235 224 236 <GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}> 225 237 <TouchableWithoutFeedback
-1
src/screens/Search/Shell.tsx
··· 428 428 const {hasSession} = useSession() 429 429 const {gtTablet} = useBreakpoints() 430 430 const [activeTab, setActiveTab] = useState(0) 431 - const {_} = useLingui() 432 431 433 432 const onPageSelected = useCallback( 434 433 (index: number) => {
-1
src/screens/Search/modules/ExploreTrendingVideos.tsx
··· 31 31 } 32 32 33 33 export function ExploreTrendingVideos() { 34 - const {_} = useLingui() 35 34 const gutters = useGutters([0, 'base']) 36 35 const {data, isLoading, error} = usePostFeedQuery(FEED_DESC, FEED_PARAMS) 37 36
+1 -1
src/screens/Settings/AppPasswords.tsx
··· 195 195 </View> 196 196 {appPassword.privileged && ( 197 197 <View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}> 198 - <WarningIcon style={[{color: colors.warning[t.scheme]}]} /> 198 + <WarningIcon style={[{color: colors.warning}]} /> 199 199 <Text style={t.atoms.text_contrast_high}> 200 200 <Trans>Allows access to direct messages</Trans> 201 201 </Text>
+2 -2
src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx
··· 134 134 <Admonition.Outer type="tip"> 135 135 <Admonition.Row> 136 136 <Admonition.Icon /> 137 - <View style={[a.flex_1, a.gap_sm]}> 137 + <Admonition.Content> 138 138 <Admonition.Text> 139 139 <Trans> 140 140 Enable notifications for an account by visiting their ··· 166 166 . 167 167 </Trans> 168 168 </Admonition.Text> 169 - </View> 169 + </Admonition.Content> 170 170 </Admonition.Row> 171 171 </Admonition.Outer> 172 172 ) : (
+2 -3
src/screens/Settings/PrivacyAndSecuritySettings.tsx
··· 1 - import {View} from 'react-native' 2 1 import {type AppBskyNotificationDeclaration} from '@atproto/api' 3 2 import {msg, Trans} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' ··· 112 111 <Admonition.Outer type="tip" style={[a.flex_1]}> 113 112 <Admonition.Row> 114 113 <Admonition.Icon /> 115 - <View style={[a.flex_1, a.gap_sm]}> 114 + <Admonition.Content> 116 115 <Admonition.Text> 117 116 <Trans> 118 117 Note: Bluesky is an open and public network. This setting ··· 131 130 <Trans>Learn more about what is public on Bluesky.</Trans> 132 131 </InlineLinkText> 133 132 </Admonition.Text> 134 - </View> 133 + </Admonition.Content> 135 134 </Admonition.Row> 136 135 </Admonition.Outer> 137 136 </SettingsList.Item>
+41 -15
src/state/feed-feedback.tsx
··· 12 12 13 13 import {PROD_FEEDS, STAGING_FEEDS} from '#/lib/constants' 14 14 import {isNetworkError} from '#/lib/hooks/useCleanError' 15 - import {logEvent} from '#/lib/statsig/statsig' 16 15 import {Logger} from '#/logger' 17 16 import { 18 17 type FeedSourceFeedInfo, ··· 28 27 29 28 export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS] 30 29 31 - export const DIRECT_FEEDBACK_INTERACTIONS = new Set< 30 + export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set< 32 31 AppBskyFeedDefs.Interaction['event'] 33 - >(['app.bsky.feed.defs#requestLess', 'app.bsky.feed.defs#requestMore']) 32 + >([ 33 + // These are explicit actions and are therefore fine to send. 34 + 'app.bsky.feed.defs#requestLess', 35 + 'app.bsky.feed.defs#requestMore', 36 + // These can be inferred from the firehose and are therefore fine to send. 37 + 'app.bsky.feed.defs#interactionLike', 38 + 'app.bsky.feed.defs#interactionQuote', 39 + 'app.bsky.feed.defs#interactionReply', 40 + 'app.bsky.feed.defs#interactionRepost', 41 + // This can be inferred from pagination requests for everything except the very last page 42 + // so it is fine to send. It is crucial for third party algorithmic feeds to receive these. 43 + 'app.bsky.feed.defs#interactionSeen', 44 + ]) 34 45 35 46 const logger = Logger.create(Logger.Context.FeedFeedback) 36 47 ··· 78 89 const aggregatedStats = useRef<AggregatedStats | null>(null) 79 90 const throttledFlushAggregatedStats = useMemo( 80 91 () => 81 - throttle(() => flushToStatsig(aggregatedStats.current), 45e3, { 82 - leading: true, // The outer call is already throttled somewhat. 83 - trailing: true, 84 - }), 85 - [], 92 + throttle( 93 + () => 94 + flushToStatsig( 95 + aggregatedStats.current, 96 + feed?.feedDescriptor ?? 'unknown', 97 + ), 98 + 45e3, 99 + { 100 + leading: true, // The outer call is already throttled somewhat. 101 + trailing: true, 102 + }, 103 + ), 104 + [feed?.feedDescriptor], 86 105 ) 87 106 88 107 const sendToFeedNoDelay = useCallback(() => { ··· 123 142 sendOrAggregateInteractionsForStats( 124 143 aggregatedStats.current, 125 144 interactionsToSend, 145 + feed?.feedDescriptor ?? 'unknown', 126 146 ) 127 147 throttledFlushAggregatedStats() 128 148 logger.debug('flushed') ··· 228 248 return false 229 249 } 230 250 const isDiscover = isDiscoverFeed(feed.feedDescriptor) 231 - return isDiscover ? true : DIRECT_FEEDBACK_INTERACTIONS.has(interaction) 251 + return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction) 232 252 } 233 253 234 254 function toString(interaction: AppBskyFeedDefs.Interaction): string { ··· 259 279 function sendOrAggregateInteractionsForStats( 260 280 stats: AggregatedStats, 261 281 interactions: AppBskyFeedDefs.Interaction[], 282 + feed: string, 262 283 ) { 263 284 for (let interaction of interactions) { 264 285 switch (interaction.event) { 265 286 // Pressing "Show more" / "Show less" is relatively uncommon so we won't aggregate them. 266 287 // This lets us send the feed context together with them. 267 288 case 'app.bsky.feed.defs#requestLess': { 268 - logEvent('discover:showLess', { 289 + logger.metric('feed:showLess', { 290 + feed, 269 291 feedContext: interaction.feedContext ?? '', 270 292 }) 271 293 break 272 294 } 273 295 case 'app.bsky.feed.defs#requestMore': { 274 - logEvent('discover:showMore', { 296 + logger.metric('feed:showMore', { 297 + feed, 275 298 feedContext: interaction.feedContext ?? '', 276 299 }) 277 300 break ··· 301 324 } 302 325 } 303 326 304 - function flushToStatsig(stats: AggregatedStats | null) { 327 + function flushToStatsig(stats: AggregatedStats | null, feedDescriptor: string) { 305 328 if (stats === null) { 306 329 return 307 330 } 308 331 309 332 if (stats.clickthroughCount > 0) { 310 - logEvent('discover:clickthrough', { 333 + logger.metric('feed:clickthrough', { 311 334 count: stats.clickthroughCount, 335 + feed: feedDescriptor, 312 336 }) 313 337 stats.clickthroughCount = 0 314 338 } 315 339 316 340 if (stats.engagedCount > 0) { 317 - logEvent('discover:engaged', { 341 + logger.metric('feed:engaged', { 318 342 count: stats.engagedCount, 343 + feed: feedDescriptor, 319 344 }) 320 345 stats.engagedCount = 0 321 346 } 322 347 323 348 if (stats.seenCount > 0) { 324 - logEvent('discover:seen', { 349 + logger.metric('feed:seen', { 325 350 count: stats.seenCount, 351 + feed: feedDescriptor, 326 352 }) 327 353 stats.seenCount = 0 328 354 }
+36 -1
src/state/geolocation/useSyncedDeviceGeolocation.ts
··· 1 1 import {useEffect, useRef} from 'react' 2 2 import * as Location from 'expo-location' 3 + import {createPermissionHook} from 'expo-modules-core' 3 4 4 5 import {logger} from '#/state/geolocation/logger' 5 6 import {getDeviceGeolocation} from '#/state/geolocation/util' 6 7 import {device, useStorage} from '#/storage' 7 8 8 9 /** 10 + * Location.useForegroundPermissions on web just errors if the navigator.permissions API is not available. 11 + * We need to catch and ignore it, since it's effectively denied. 12 + * @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293 13 + */ 14 + const useForegroundPermissions = createPermissionHook({ 15 + getMethod: () => 16 + Location.getForegroundPermissionsAsync().catch(error => { 17 + logger.debug( 18 + 'useForegroundPermission: error getting location permissions', 19 + {safeMessage: error}, 20 + ) 21 + return { 22 + status: Location.PermissionStatus.DENIED, 23 + granted: false, 24 + canAskAgain: false, 25 + expires: 0, 26 + } 27 + }), 28 + requestMethod: () => 29 + Location.requestForegroundPermissionsAsync().catch(error => { 30 + logger.debug( 31 + 'useForegroundPermission: error requesting location permissions', 32 + {safeMessage: error}, 33 + ) 34 + return { 35 + status: Location.PermissionStatus.DENIED, 36 + granted: false, 37 + canAskAgain: false, 38 + expires: 0, 39 + } 40 + }), 41 + }) 42 + 43 + /** 9 44 * Hook to get and sync the device geolocation from the device GPS and store it 10 45 * using device storage. If permissions are not granted, it will clear any cached 11 46 * storage value. 12 47 */ 13 48 export function useSyncedDeviceGeolocation() { 14 49 const synced = useRef(false) 15 - const [status] = Location.useForegroundPermissions() 50 + const [status] = useForegroundPermissions() 16 51 const [deviceGeolocation, setDeviceGeolocation] = useStorage(device, [ 17 52 'deviceGeolocation', 18 53 ])
+1 -1
src/state/persisted/schema.ts
··· 71 71 contentLanguages: z.array(z.string()), 72 72 /** 73 73 * The language(s) the user is currently posting in, configured within the 74 - * composer. Multiple languages are psearate by commas. 74 + * composer. Multiple languages are separated by commas. 75 75 * 76 76 * BCP-47 2-letter language code without region. 77 77 */
+4
src/state/preferences/languages.tsx
··· 156 156 return postLanguage.split(',').filter(Boolean) 157 157 } 158 158 159 + export function fromPostLanguages(languages: string[]): string { 160 + return languages.filter(Boolean).join(',') 161 + } 162 + 159 163 export function hasPostLanguage(postLanguage: string, code2: string): boolean { 160 164 return toPostLanguages(postLanguage).includes(code2) 161 165 }
+5 -5
src/state/queries/post-feed.ts
··· 492 492 } 493 493 } 494 494 } else if (feedDesc.startsWith('author')) { 495 - const [_, actor, filter] = feedDesc.split('|') 495 + const [__, actor, filter] = feedDesc.split('|') 496 496 return new AuthorFeedAPI({agent, feedParams: {actor, filter}}) 497 497 } else if (feedDesc.startsWith('likes')) { 498 - const [_, actor] = feedDesc.split('|') 498 + const [__, actor] = feedDesc.split('|') 499 499 return new LikesFeedAPI({agent, feedParams: {actor}}) 500 500 } else if (feedDesc.startsWith('feedgen')) { 501 - const [_, feed] = feedDesc.split('|') 501 + const [__, feed] = feedDesc.split('|') 502 502 return new CustomFeedAPI({ 503 503 agent, 504 504 feedParams: {feed}, 505 505 userInterests, 506 506 }) 507 507 } else if (feedDesc.startsWith('list')) { 508 - const [_, list] = feedDesc.split('|') 508 + const [__, list] = feedDesc.split('|') 509 509 return new ListFeedAPI({agent, feedParams: {list}}) 510 510 } else if (feedDesc.startsWith('posts')) { 511 - const [_, uriList] = feedDesc.split('|') 511 + const [__, uriList] = feedDesc.split('|') 512 512 return new PostListFeedAPI({agent, feedParams: {uris: uriList.split(',')}}) 513 513 } else if (feedDesc === 'demo') { 514 514 return new DemoFeedAPI({agent})
+1 -1
src/state/queries/trending/useGetSuggestedUsersQuery.ts
··· 69 69 queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsers.OutputSchema>({ 70 70 queryKey: [getSuggestedUsersQueryKeyRoot], 71 71 }) 72 - for (const [_, response] of responses) { 72 + for (const [_key, response] of responses) { 73 73 if (!response) { 74 74 continue 75 75 }
+55 -6
src/view/com/composer/Composer.tsx
··· 88 88 import {useModalControls} from '#/state/modals' 89 89 import {useRequireAltTextEnabled} from '#/state/preferences' 90 90 import { 91 + fromPostLanguages, 91 92 toPostLanguages, 92 93 useLanguagePrefs, 93 94 useLanguagePrefsApi, ··· 197 198 const [publishingStage, setPublishingStage] = useState('') 198 199 const [error, setError] = useState('') 199 200 201 + /** 202 + * A temporary local reference to a language suggestion that the user has 203 + * accepted. This overrides the global post language preference, but is not 204 + * stored permanently. 205 + */ 206 + const [acceptedLanguageSuggestion, setAcceptedLanguageSuggestion] = useState< 207 + string | null 208 + >(null) 209 + 210 + /** 211 + * The language(s) of the post being replied to. 212 + */ 213 + const [replyToLanguages, setReplyToLanguages] = useState<string[]>( 214 + replyTo?.langs || [], 215 + ) 216 + 217 + /** 218 + * The currently selected languages of the post. Prefer local temporary 219 + * language suggestion over global lang prefs, if available. 220 + */ 221 + const currentLanguages = useMemo( 222 + () => 223 + acceptedLanguageSuggestion 224 + ? [acceptedLanguageSuggestion] 225 + : toPostLanguages(langPrefs.postLanguage), 226 + [acceptedLanguageSuggestion, langPrefs.postLanguage], 227 + ) 228 + 229 + /** 230 + * When the user selects a language from the composer language selector, 231 + * clear any temporary language suggestions they may have selected 232 + * previously, and any we might try to suggest to them. 233 + */ 234 + const onSelectLanguage = () => { 235 + setAcceptedLanguageSuggestion(null) 236 + setReplyToLanguages([]) 237 + } 238 + 200 239 const [composerState, composerDispatch] = useReducer( 201 240 composerReducer, 202 241 { ··· 414 453 thread, 415 454 replyTo: replyTo?.uri, 416 455 onStateChange: setPublishingStage, 417 - langs: toPostLanguages(langPrefs.postLanguage), 456 + langs: currentLanguages, 418 457 }) 419 458 ).uris[0] 420 459 ··· 490 529 isPartOfThread: thread.posts.length > 1, 491 530 hasLink: !!post.embed.link, 492 531 hasQuote: !!post.embed.quote, 493 - langs: langPrefs.postLanguage, 532 + langs: fromPostLanguages(currentLanguages), 494 533 logContext: 'Composer', 495 534 }) 496 535 index++ ··· 557 596 thread, 558 597 canPost, 559 598 isPublishing, 560 - langPrefs.postLanguage, 599 + currentLanguages, 561 600 onClose, 562 601 onPost, 563 602 onPostSuccess, ··· 654 693 <> 655 694 <SuggestedLanguage 656 695 text={activePost.richtext.text} 657 - // NOTE(@elijaharita): currently just choosing the first language if any exists 658 - replyToLanguage={replyTo?.langs?.[0]} 696 + replyToLanguages={replyToLanguages} 697 + currentLanguages={currentLanguages} 698 + onAcceptSuggestedLanguage={setAcceptedLanguageSuggestion} 659 699 /> 660 700 <ComposerPills 661 701 isReply={!!replyTo} ··· 678 718 type: 'add_post', 679 719 }) 680 720 }} 721 + currentLanguages={currentLanguages} 722 + onSelectLanguage={onSelectLanguage} 681 723 /> 682 724 </> 683 725 ) ··· 1289 1331 onEmojiButtonPress, 1290 1332 onSelectVideo, 1291 1333 onAddPost, 1334 + currentLanguages, 1335 + onSelectLanguage, 1292 1336 }: { 1293 1337 post: PostDraft 1294 1338 dispatch: (action: PostAction) => void ··· 1297 1341 onError: (error: string) => void 1298 1342 onSelectVideo: (postId: string, asset: ImagePickerAsset) => void 1299 1343 onAddPost: () => void 1344 + currentLanguages: string[] 1345 + onSelectLanguage?: (language: string) => void 1300 1346 }) { 1301 1347 const t = useTheme() 1302 1348 const {_} = useLingui() ··· 1450 1496 <PlusIcon size="lg" /> 1451 1497 </Button> 1452 1498 )} 1453 - <PostLanguageSelect /> 1499 + <PostLanguageSelect 1500 + currentLanguages={currentLanguages} 1501 + onSelectLanguage={onSelectLanguage} 1502 + /> 1454 1503 <CharProgress 1455 1504 count={post.shortenedGraphemeLength} 1456 1505 style={{width: 65}}
-1
src/view/com/composer/photos/EditImageDialog.web.tsx
··· 116 116 }) { 117 117 const t = useTheme() 118 118 const [isDragging, setIsDragging] = useState(false) 119 - const {_} = useLingui() 120 119 const control = Dialog.useDialogContext() 121 120 122 121 const source = image.source
+35 -9
src/view/com/composer/select-language/PostLanguageSelect.tsx
··· 17 17 import {Text} from '#/components/Typography' 18 18 import {PostLanguageSelectDialog} from './PostLanguageSelectDialog' 19 19 20 - export function PostLanguageSelect() { 20 + export function PostLanguageSelect({ 21 + currentLanguages: currentLanguagesProp, 22 + onSelectLanguage, 23 + }: { 24 + currentLanguages?: string[] 25 + onSelectLanguage?: (language: string) => void 26 + }) { 21 27 const {_} = useLingui() 22 28 const langPrefs = useLanguagePrefs() 23 29 const setLangPrefs = useLanguagePrefsApi() ··· 26 32 const dedupedHistory = Array.from( 27 33 new Set([...langPrefs.postLanguageHistory, langPrefs.postLanguage]), 28 34 ) 35 + 36 + const currentLanguages = 37 + currentLanguagesProp ?? toPostLanguages(langPrefs.postLanguage) 29 38 30 39 if ( 31 40 dedupedHistory.length === 1 && ··· 34 43 return ( 35 44 <> 36 45 <LanguageBtn onPress={languageDialogControl.open} /> 37 - <PostLanguageSelectDialog control={languageDialogControl} /> 46 + <PostLanguageSelectDialog 47 + control={languageDialogControl} 48 + currentLanguages={currentLanguages} 49 + /> 38 50 </> 39 51 ) 40 52 } ··· 43 55 <> 44 56 <Menu.Root> 45 57 <Menu.Trigger label={_(msg`Select post language`)}> 46 - {({props}) => <LanguageBtn {...props} />} 58 + {({props}) => ( 59 + <LanguageBtn currentLanguages={currentLanguages} {...props} /> 60 + )} 47 61 </Menu.Trigger> 48 62 <Menu.Outer> 49 63 <Menu.Group> ··· 56 70 <Menu.Item 57 71 key={historyItem} 58 72 label={_(msg`Select ${langName}`)} 59 - onPress={() => setLangPrefs.setPostLanguage(historyItem)}> 73 + onPress={() => { 74 + setLangPrefs.setPostLanguage(historyItem) 75 + onSelectLanguage?.(historyItem) 76 + }}> 60 77 <Menu.ItemText>{langName}</Menu.ItemText> 61 78 <Menu.ItemRadio 62 - selected={historyItem === langPrefs.postLanguage} 79 + selected={currentLanguages.includes(historyItem)} 63 80 /> 64 81 </Menu.Item> 65 82 ) ··· 77 94 </Menu.Outer> 78 95 </Menu.Root> 79 96 80 - <PostLanguageSelectDialog control={languageDialogControl} /> 97 + <PostLanguageSelectDialog 98 + control={languageDialogControl} 99 + currentLanguages={currentLanguages} 100 + onSelectLanguage={onSelectLanguage} 101 + /> 81 102 </> 82 103 ) 83 104 } 84 105 85 - function LanguageBtn(props: Omit<ButtonProps, 'label' | 'children'>) { 106 + function LanguageBtn( 107 + props: Omit<ButtonProps, 'label' | 'children'> & { 108 + currentLanguages?: string[] 109 + }, 110 + ) { 86 111 const {_} = useLingui() 87 112 const langPrefs = useLanguagePrefs() 88 113 const t = useTheme() 89 114 90 115 const postLanguagesPref = toPostLanguages(langPrefs.postLanguage) 116 + const currentLanguages = props.currentLanguages ?? postLanguagesPref 91 117 92 118 return ( 93 119 <Button ··· 106 132 {({pressed, hovered}) => { 107 133 const color = 108 134 pressed || hovered ? t.palette.primary_300 : t.palette.primary_500 109 - if (postLanguagesPref.length > 0) { 135 + if (currentLanguages.length > 0) { 110 136 return ( 111 137 <Text 112 138 style={[ ··· 117 143 {maxWidth: 100}, 118 144 ]} 119 145 numberOfLines={1}> 120 - {postLanguagesPref 146 + {currentLanguages 121 147 .map(lang => codeToLanguageName(lang, langPrefs.appLanguage)) 122 148 .join(', ')} 123 149 </Text>
+25 -3
src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
··· 8 8 import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages' 9 9 import {isNative, isWeb} from '#/platform/detection' 10 10 import { 11 + toPostLanguages, 11 12 useLanguagePrefs, 12 13 useLanguagePrefsApi, 13 14 } from '#/state/preferences/languages' ··· 23 24 24 25 export function PostLanguageSelectDialog({ 25 26 control, 27 + /** 28 + * Optionally can be passed to show different values than what is saved in 29 + * langPrefs. 30 + */ 31 + currentLanguages, 32 + onSelectLanguage, 26 33 }: { 27 34 control: Dialog.DialogControlProps 35 + currentLanguages?: string[] 36 + onSelectLanguage?: (language: string) => void 28 37 }) { 29 38 const {height} = useWindowDimensions() 30 39 const insets = useSafeAreaInsets() ··· 40 49 nativeOptions={{minHeight: height - insets.top}}> 41 50 <Dialog.Handle /> 42 51 <ErrorBoundary renderError={renderErrorBoundary}> 43 - <DialogInner /> 52 + <DialogInner 53 + currentLanguages={currentLanguages} 54 + onSelectLanguage={onSelectLanguage} 55 + /> 44 56 </ErrorBoundary> 45 57 </Dialog.Outer> 46 58 ) 47 59 } 48 60 49 - export function DialogInner() { 61 + export function DialogInner({ 62 + currentLanguages, 63 + onSelectLanguage, 64 + }: { 65 + currentLanguages?: string[] 66 + onSelectLanguage?: (language: string) => void 67 + }) { 50 68 const control = Dialog.useDialogContext() 51 69 const [headerHeight, setHeaderHeight] = useState(0) 52 70 ··· 63 81 }, []) 64 82 65 83 const langPrefs = useLanguagePrefs() 84 + const postLanguagesPref = 85 + currentLanguages ?? toPostLanguages(langPrefs.postLanguage) 86 + 66 87 const [checkedLanguagesCode2, setCheckedLanguagesCode2] = useState<string[]>( 67 - langPrefs.postLanguage.split(',') || [langPrefs.primaryLanguage], 88 + postLanguagesPref || [langPrefs.primaryLanguage], 68 89 ) 69 90 const [search, setSearch] = useState('') 70 91 ··· 79 100 langsString = langPrefs.primaryLanguage 80 101 } 81 102 setLangPrefs.setPostLanguage(langsString) 103 + onSelectLanguage?.(langsString) 82 104 }) 83 105 } 84 106
+121 -44
src/view/com/composer/select-language/SuggestedLanguage.tsx
··· 1 1 import {useEffect, useState} from 'react' 2 - import {View} from 'react-native' 2 + import {Text as RNText, View} from 'react-native' 3 3 import {parseLanguage} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 import lande from 'lande' 7 7 8 8 import {code3ToCode2Strict, codeToLanguageName} from '#/locale/helpers' 9 - import { 10 - toPostLanguages, 11 - useLanguagePrefs, 12 - useLanguagePrefsApi, 13 - } from '#/state/preferences/languages' 9 + import {useLanguagePrefs} from '#/state/preferences/languages' 14 10 import {atoms as a, useTheme} from '#/alf' 15 11 import {Button, ButtonText} from '#/components/Button' 16 12 import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' ··· 22 18 23 19 export function SuggestedLanguage({ 24 20 text, 25 - replyToLanguage: replyToLanguageProp, 21 + replyToLanguages: replyToLanguagesProp, 22 + currentLanguages, 23 + onAcceptSuggestedLanguage, 26 24 }: { 27 25 text: string 28 - replyToLanguage?: string 26 + /** 27 + * All languages associated with the post being replied to. 28 + */ 29 + replyToLanguages: string[] 30 + /** 31 + * All languages currently selected for the post being composed. 32 + */ 33 + currentLanguages: string[] 34 + /** 35 + * Called when the user accepts a suggested language. We only pass a single 36 + * language here. If the post being replied to has multiple languages, we 37 + * only suggest the first one. 38 + */ 39 + onAcceptSuggestedLanguage: (language: string | null) => void 29 40 }) { 30 - const replyToLanguage = cleanUpLanguage(replyToLanguageProp) 41 + const langPrefs = useLanguagePrefs() 42 + const replyToLanguages = replyToLanguagesProp 43 + .map(lang => cleanUpLanguage(lang)) 44 + .filter(Boolean) as string[] 45 + const [hasInteracted, setHasInteracted] = useState(false) 31 46 const [suggestedLanguage, setSuggestedLanguage] = useState< 32 47 string | undefined 33 - >(text.length === 0 ? replyToLanguage : undefined) 34 - const langPrefs = useLanguagePrefs() 35 - const setLangPrefs = useLanguagePrefsApi() 36 - const t = useTheme() 37 - const {_} = useLingui() 48 + >(undefined) 38 49 39 50 useEffect(() => { 40 - // For replies, suggest the language of the post being replied to if no text 41 - // has been typed yet 42 - if (replyToLanguage && text.length === 0) { 43 - setSuggestedLanguage(replyToLanguage) 44 - return 51 + if (text.length > 0 && !hasInteracted) { 52 + setHasInteracted(true) 45 53 } 54 + }, [text, hasInteracted]) 46 55 56 + useEffect(() => { 47 57 const textTrimmed = text.trim() 48 58 49 59 // Don't run the language model on small posts, the results are likely ··· 58 68 }) 59 69 60 70 return () => cancelIdle(idle) 61 - }, [text, replyToLanguage]) 71 + }, [text]) 72 + 73 + /* 74 + * We've detected a language, and the user hasn't already selected it. 75 + */ 76 + const hasLanguageSuggestion = 77 + suggestedLanguage && !currentLanguages.includes(suggestedLanguage) 78 + /* 79 + * We have not detected a different language, and the user is not already 80 + * using or has not already selected one of the languages of the post they 81 + * are replying to. 82 + */ 83 + const hasSuggestedReplyLanguage = 84 + !hasInteracted && 85 + !suggestedLanguage && 86 + replyToLanguages.length && 87 + !replyToLanguages.some(l => currentLanguages.includes(l)) 62 88 63 - if ( 64 - suggestedLanguage && 65 - !toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage) 66 - ) { 89 + if (hasLanguageSuggestion) { 67 90 const suggestedLanguageName = codeToLanguageName( 68 91 suggestedLanguage, 69 92 langPrefs.appLanguage, 70 93 ) 71 94 72 95 return ( 96 + <LanguageSuggestionButton 97 + label={ 98 + <RNText> 99 + <Trans> 100 + Are you writing in{' '} 101 + <Text style={[a.font_bold]}>{suggestedLanguageName}</Text>? 102 + </Trans> 103 + </RNText> 104 + } 105 + value={suggestedLanguage} 106 + onAccept={onAcceptSuggestedLanguage} 107 + /> 108 + ) 109 + } else if (hasSuggestedReplyLanguage) { 110 + const suggestedLanguageName = codeToLanguageName( 111 + replyToLanguages[0], 112 + langPrefs.appLanguage, 113 + ) 114 + 115 + return ( 116 + <LanguageSuggestionButton 117 + label={ 118 + <RNText> 119 + <Trans> 120 + The post you're replying to was marked as being written in{' '} 121 + {suggestedLanguageName} by its author. Would you like to reply in{' '} 122 + <Text style={[a.font_bold]}>{suggestedLanguageName}</Text>? 123 + </Trans> 124 + </RNText> 125 + } 126 + value={replyToLanguages[0]} 127 + onAccept={onAcceptSuggestedLanguage} 128 + /> 129 + ) 130 + } else { 131 + return null 132 + } 133 + } 134 + 135 + function LanguageSuggestionButton({ 136 + label, 137 + value, 138 + onAccept, 139 + }: { 140 + label: React.ReactNode 141 + value: string 142 + onAccept: (language: string | null) => void 143 + }) { 144 + const t = useTheme() 145 + const {_} = useLingui() 146 + 147 + return ( 148 + <View style={[a.px_lg, a.py_sm]}> 73 149 <View 74 150 style={[ 75 - t.atoms.border_contrast_low, 76 - a.gap_sm, 151 + a.gap_md, 77 152 a.border, 78 153 a.flex_row, 79 154 a.align_center, 80 155 a.rounded_sm, 81 - a.px_lg, 82 - a.py_md, 83 - a.mx_md, 84 - a.my_sm, 156 + a.p_md, 157 + a.pl_lg, 85 158 t.atoms.bg, 159 + t.atoms.border_contrast_low, 86 160 ]}> 87 161 <EarthIcon /> 88 - <Text style={[a.flex_1]}> 89 - <Trans> 90 - Are you writing in{' '} 91 - <Text style={[a.font_semi_bold]}>{suggestedLanguageName}</Text>? 92 - </Trans> 93 - </Text> 162 + <View style={[a.flex_1]}> 163 + <Text 164 + style={[ 165 + a.flex_1, 166 + a.leading_snug, 167 + { 168 + maxWidth: 400, 169 + }, 170 + ]}> 171 + {label} 172 + </Text> 173 + </View> 94 174 95 175 <Button 96 - color="secondary" 97 176 size="small" 98 - variant="solid" 99 - onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)} 100 - label={_(msg`Change post language to ${suggestedLanguageName}`)}> 177 + color="secondary" 178 + onPress={() => onAccept(value)} 179 + label={_(msg`Accept this language suggestion`)}> 101 180 <ButtonText> 102 181 <Trans>Yes</Trans> 103 182 </ButtonText> 104 183 </Button> 105 184 </View> 106 - ) 107 - } else { 108 - return null 109 - } 185 + </View> 186 + ) 110 187 } 111 188 112 189 /**
+1 -1
src/view/com/composer/text-input/web/TagDecorator.ts
··· 30 30 31 31 let match 32 32 while ((match = regex.exec(textContent))) { 33 - const [matchedString, _, tag] = match 33 + const [matchedString, __, tag] = match 34 34 35 35 if (!tag || tag.replace(TRAILING_PUNCTUATION_REGEX, '').length > 64) 36 36 continue
+1 -1
src/view/com/posts/PostFeedErrorMessage.tsx
··· 126 126 })[knownError], 127 127 [_l, knownError], 128 128 ) 129 - const [_, uri] = feedDesc.split('|') 129 + const [__, uri] = feedDesc.split('|') 130 130 const [ownerDid] = safeParseFeedgenUri(uri) 131 131 const removePromptControl = Prompt.usePromptControl() 132 132 const {mutateAsync: removeFeed} = useRemoveFeedMutation()
-2
src/view/screens/ModerationMutedAccounts.tsx
··· 2 2 import {type StyleProp, View, type ViewStyle} from 'react-native' 3 3 import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 4 4 import {Trans} from '@lingui/macro' 5 - import {useLingui} from '@lingui/react' 6 5 import {useFocusEffect} from '@react-navigation/native' 7 6 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 8 7 ··· 27 26 export function ModerationMutedAccounts({}: Props) { 28 27 const t = useTheme() 29 28 const moderationOpts = useModerationOpts() 30 - const {_} = useLingui() 31 29 const setMinimalShellMode = useSetMinimalShellMode() 32 30 33 31 const [isPTRing, setIsPTRing] = useState(false)
-2
src/view/screens/ProfileFeedLikedBy.tsx
··· 1 1 import {useCallback} from 'react' 2 2 import {Trans} from '@lingui/macro' 3 - import {useLingui} from '@lingui/react' 4 3 import {useFocusEffect} from '@react-navigation/native' 5 4 6 5 import { ··· 17 16 const setMinimalShellMode = useSetMinimalShellMode() 18 17 const {name, rkey} = route.params 19 18 const uri = makeRecordUri(name, 'app.bsky.feed.generator', rkey) 20 - const {_} = useLingui() 21 19 22 20 useFocusEffect( 23 21 useCallback(() => {
+74 -3
src/view/screens/Storybook/Admonitions.tsx
··· 1 - import {View} from 'react-native' 1 + import {Text as RNText, View} from 'react-native' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 2 4 3 - import {atoms as a} from '#/alf' 4 - import {Admonition} from '#/components/Admonition' 5 + import {atoms as a, useTheme} from '#/alf' 6 + import { 7 + Admonition, 8 + Button as AdmonitionButton, 9 + Content as AdmonitionContent, 10 + Icon as AdmonitionIcon, 11 + Outer as AdmonitionOuter, 12 + Row as AdmonitionRow, 13 + Text as AdmonitionText, 14 + } from '#/components/Admonition' 15 + import {ButtonIcon, ButtonText} from '#/components/Button' 16 + import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise' 17 + import {BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon} from '#/components/icons/BellRinging' 5 18 import {InlineLinkText} from '#/components/Link' 6 19 import {H1} from '#/components/Typography' 7 20 8 21 export function Admonitions() { 22 + const {_} = useLingui() 23 + const t = useTheme() 24 + 9 25 return ( 10 26 <View style={[a.gap_md]}> 11 27 <H1>Admonitions</H1> ··· 30 46 <Admonition type="error"> 31 47 The quick brown fox jumps over the lazy dog. 32 48 </Admonition> 49 + 50 + <AdmonitionOuter type="error"> 51 + <AdmonitionRow> 52 + <AdmonitionIcon /> 53 + <AdmonitionContent> 54 + <AdmonitionText> 55 + <Trans>Something went wrong, please try again</Trans> 56 + </AdmonitionText> 57 + </AdmonitionContent> 58 + <AdmonitionButton 59 + color="negative_subtle" 60 + label={_(msg`Retry loading report options`)} 61 + onPress={() => {}}> 62 + <ButtonText> 63 + <Trans>Retry</Trans> 64 + </ButtonText> 65 + <ButtonIcon icon={Retry} /> 66 + </AdmonitionButton> 67 + </AdmonitionRow> 68 + </AdmonitionOuter> 69 + 70 + <AdmonitionOuter type="tip"> 71 + <AdmonitionRow> 72 + <AdmonitionIcon /> 73 + <AdmonitionContent> 74 + <AdmonitionText> 75 + <Trans> 76 + Enable notifications for an account by visiting their profile 77 + and pressing the{' '} 78 + <RNText style={[a.font_bold, t.atoms.text_contrast_high]}> 79 + bell icon 80 + </RNText>{' '} 81 + <BellRingingFilledIcon 82 + size="xs" 83 + style={t.atoms.text_contrast_high} 84 + /> 85 + . 86 + </Trans> 87 + </AdmonitionText> 88 + <AdmonitionText> 89 + <Trans> 90 + If you want to restrict who can receive notifications for your 91 + account's activity, you can change this in{' '} 92 + <InlineLinkText 93 + label={_(msg`Privacy and Security settings`)} 94 + to={{screen: 'ActivityPrivacySettings'}} 95 + style={[a.font_bold]}> 96 + Settings &rarr; Privacy and Security 97 + </InlineLinkText> 98 + . 99 + </Trans> 100 + </AdmonitionText> 101 + </AdmonitionContent> 102 + </AdmonitionRow> 103 + </AdmonitionOuter> 33 104 </View> 34 105 ) 35 106 }
+25
src/view/screens/Storybook/Buttons.tsx
··· 8 8 ButtonIcon, 9 9 type ButtonSize, 10 10 ButtonText, 11 + StackedButton, 11 12 } from '#/components/Button' 12 13 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' 13 14 import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' ··· 17 18 return ( 18 19 <View style={[a.gap_md]}> 19 20 <Text style={[a.font_bold, a.text_5xl]}>Buttons</Text> 21 + 22 + <View style={[a.flex_row, a.gap_md, a.align_start, {maxWidth: 350}]}> 23 + <StackedButton 24 + label="stacked" 25 + icon={Globe} 26 + color="secondary" 27 + style={[a.flex_1]}> 28 + Bop it 29 + </StackedButton> 30 + <StackedButton 31 + label="stacked" 32 + icon={Globe} 33 + color="negative_subtle" 34 + style={[a.flex_1]}> 35 + Twist it 36 + </StackedButton> 37 + <StackedButton 38 + label="stacked" 39 + icon={Globe} 40 + color="primary" 41 + style={[a.flex_1]}> 42 + Pull it 43 + </StackedButton> 44 + </View> 20 45 21 46 {[ 22 47 'primary',
+5 -17
yarn.lock
··· 8646 8646 dependencies: 8647 8647 "@babel/helper-define-polyfill-provider" "^0.6.3" 8648 8648 8649 - babel-plugin-react-compiler@^19.1.0-rc.1: 8650 - version "19.1.0-rc.1" 8651 - resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.1.tgz#99d131be61017e40abbaedd98321069bf8b7e54a" 8652 - integrity sha512-M4fpG+Hfq5gWzsJeeMErdRokzg0fdJ8IAk+JDhfB/WLT+U3WwJWR8edphypJrk447/JEvYu6DBFwsTn10bMW4Q== 8653 - dependencies: 8654 - "@babel/types" "^7.26.0" 8655 - 8656 - babel-plugin-react-compiler@^19.1.0-rc.2: 8649 + babel-plugin-react-compiler@^19.1.0-rc.2, babel-plugin-react-compiler@^19.1.0-rc.3: 8657 8650 version "19.1.0-rc.3" 8658 8651 resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.3.tgz#45e5a282a2460b3701971e5eb8310a90a7919022" 8659 8652 integrity sha512-mjRn69WuTz4adL0bXGx8Rsyk1086zFJeKmes6aK0xPuK3aaXmDJdLHqwKKMrpm6KAI1MCoUK72d2VeqQbu8YIA== ··· 10867 10860 dependencies: 10868 10861 "@typescript-eslint/utils" "^5.61.0" 10869 10862 10870 - eslint-plugin-react-compiler@^19.1.0-rc.1: 10871 - version "19.1.0-rc.1" 10872 - resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.1.0-rc.1.tgz#e974ba9541c9a4464d77723e0505b5742bc22e56" 10873 - integrity sha512-3umw5eqZXapBl7aQGmvcjheKhUbsElb9jTETxRZg371e1LG4EPs/zCHt2JzP+wNcdaZWzjU/R730zPUJblY2zw== 10863 + eslint-plugin-react-compiler@^19.1.0-rc.2: 10864 + version "19.1.0-rc.2" 10865 + resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.1.0-rc.2.tgz#83343e7422e00fa61e729af8e8468f0ddec37925" 10866 + integrity sha512-oKalwDGcD+RX9mf3NEO4zOoUMeLvjSvcbbEOpquzmzqEEM2MQdp7/FY/Hx9NzmUwFzH1W9SKTz5fihfMldpEYw== 10874 10867 dependencies: 10875 10868 "@babel/core" "^7.24.4" 10876 10869 "@babel/parser" "^7.24.4" ··· 17113 17106 convert-source-map "^2.0.0" 17114 17107 invariant "^2.2.4" 17115 17108 react-native-is-edge-to-edge "1.1.7" 17116 - 17117 - react-native-root-siblings@^5.0.1: 17118 - version "5.0.1" 17119 - resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c" 17120 - integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g== 17121 17109 17122 17110 react-native-safe-area-context@~5.6.0: 17123 17111 version "5.6.1"