Bluesky app fork with some witchin' additions 💫

Merge with bluesky-social/social-app again

3rd attempt to get dumb germ dm button and various upstream refactoring stuff in (I hope for more dm buttons in the future) also removing .github folder again since this is a .tangled household

xan.lol 5efb0d1a 711c6982

verified
+1355 -644
-54
.github/workflows/claude.yml
··· 1 - name: Claude Code 2 - 3 - on: 4 - issue_comment: 5 - types: [created] 6 - pull_request_review_comment: 7 - types: [created] 8 - issues: 9 - types: [opened, assigned] 10 - pull_request_review: 11 - types: [submitted] 12 - 13 - jobs: 14 - claude: 15 - if: | 16 - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) 20 - runs-on: ubuntu-latest 21 - permissions: 22 - contents: read 23 - pull-requests: read 24 - issues: read 25 - id-token: write 26 - actions: read # Required for Claude to read CI results on PRs 27 - steps: 28 - - name: Checkout repository 29 - uses: actions/checkout@v4 30 - with: 31 - fetch-depth: 1 32 - 33 - - name: Run Claude Code 34 - id: claude 35 - uses: anthropics/claude-code-action@v1 36 - with: 37 - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 38 - 39 - # This is an optional setting that allows Claude to read CI results on PRs 40 - additional_permissions: | 41 - actions: read 42 - 43 - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. 44 - # prompt: 'Update the pull request description to include a summary of changes.' 45 - 46 - # Optional: Add claude_args to customize behavior and configuration 47 - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md 48 - # or https://code.claude.com/docs/en/cli-reference for available options 49 - # claude_args: '--allowed-tools Bash(gh pr:*)' 50 - 51 - # NOTE(sfn): we can add a custom system prompt here 52 - 53 - claude_args: | 54 - --model claude-opus-4-5-20251101
···
+1
assets/icons/person_filled_corner2_rounded.svg
···
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M12.233 2a4.433 4.433 0 1 0 0 8.867 4.433 4.433 0 0 0 0-8.867Zm0 10.133c-3.888 0-6.863 2.263-8.071 5.435-.346.906-.11 1.8.44 2.436.535.619 1.36.996 2.25.996h10.762c.89 0 1.716-.377 2.25-.996.55-.636.786-1.53.441-2.436-1.208-3.173-4.184-5.435-8.072-5.435Z"/></svg>
assets/images/germ_logo.webp

This is a binary file and will not be displayed.

+4 -4
bskyweb/templates/base.html
··· 148 </head> 149 <body> 150 {%- block body_all %} 151 <div id="root"> 152 - <div id="splash"> 153 - <!-- Bluesky SVG --> 154 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 320"><path fill="#0085ff" d="M180 142c-16.3-31.7-60.7-90.8-102-120C38.5-5.9 23.4-1 13.5 3.4 2.1 8.6 0 26.2 0 36.5c0 10.4 5.7 84.8 9.4 97.2 12.2 41 55.7 55 95.7 50.5-58.7 8.6-110.8 30-42.4 106.1 75.1 77.9 103-16.7 117.3-64.6 14.3 48 30.8 139 116 64.6 64-64.6 17.6-97.5-41.1-106.1 40 4.4 83.5-9.5 95.7-50.5 3.7-12.4 9.4-86.8 9.4-97.2 0-10.3-2-27.9-13.5-33C336.5-1 321.5-6 282 22c-41.3 29.2-85.7 88.3-102 120Z"/></svg><!-- TODO: Xan: Replace with Witchsky logo --> 155 - </div> 156 </div> 157 158 <noscript>
··· 148 </head> 149 <body> 150 {%- block body_all %} 151 + <div id="splash"> 152 + <!-- Witchsky SVG --> 153 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#ED5345" d="M374.473 57.7173C367.666 50.7995 357.119 49.1209 348.441 53.1659C347.173 53.7567 342.223 56.0864 334.796 59.8613C326.32 64.1696 314.568 70.3869 301.394 78.0596C275.444 93.1728 242.399 114.83 218.408 139.477C185.983 172.786 158.719 225.503 140.029 267.661C130.506 289.144 122.878 308.661 117.629 322.81C116.301 326.389 115.124 329.63 114.104 332.478C87.1783 336.42 64.534 341.641 47.5078 348.101C37.6493 351.84 28.3222 356.491 21.0573 362.538C13.8818 368.511 6.00003 378.262 6.00003 391.822C6.00014 403.222 11.8738 411.777 17.4566 417.235C23.0009 422.655 29.9593 426.793 36.871 430.062C50.8097 436.653 69.5275 441.988 90.8362 446.249C133.828 454.846 192.21 460 256.001 460C319.79 460 378.172 454.846 421.164 446.249C442.472 441.988 461.19 436.653 475.129 430.062C482.041 426.793 488.999 422.655 494.543 417.235C500.039 411.862 505.817 403.489 505.996 392.353L506 391.822L505.995 391.188C505.754 377.959 498.012 368.417 490.945 362.534C483.679 356.485 474.35 351.835 464.491 348.095C446.749 341.366 422.906 335.982 394.476 331.987C393.6 330.57 392.633 328.995 391.595 327.273C386.477 318.777 379.633 306.842 372.737 293.115C358.503 264.781 345.757 232.098 344.756 206.636C343.87 184.121 351.638 154.087 360.819 127.789C365.27 115.041 369.795 103.877 373.207 95.9072C374.909 91.9309 376.325 88.7712 377.302 86.6328C377.79 85.5645 378.167 84.7524 378.416 84.2224C378.54 83.9579 378.632 83.7635 378.69 83.643C378.718 83.5829 378.739 83.5411 378.75 83.5181C378.753 83.5108 378.756 83.5049 378.757 83.5015C382.909 74.8634 381.196 64.5488 374.473 57.7173Z"/></svg> 154 + </div> 155 <div id="root"> 156 </div> 157 158 <noscript>
+2 -2
package.json
··· 73 "icons:optimize": "svgo -f ./assets/icons" 74 }, 75 "dependencies": { 76 - "@atproto/api": "^0.18.20", 77 "@bitdrift/react-native": "^0.6.8", 78 "@braintree/sanitize-url": "^6.0.2", 79 "@bsky.app/alf": "^0.1.6", ··· 230 "zod": "^3.20.2" 231 }, 232 "devDependencies": { 233 - "@atproto/dev-env": "^0.3.208", 234 "@babel/core": "^7.26.0", 235 "@babel/preset-env": "^7.26.0", 236 "@babel/runtime": "^7.26.0",
··· 73 "icons:optimize": "svgo -f ./assets/icons" 74 }, 75 "dependencies": { 76 + "@atproto/api": "^0.18.21", 77 "@bitdrift/react-native": "^0.6.8", 78 "@braintree/sanitize-url": "^6.0.2", 79 "@bsky.app/alf": "^0.1.6", ··· 230 "zod": "^3.20.2" 231 }, 232 "devDependencies": { 233 + "@atproto/dev-env": "^0.3.209", 234 "@babel/core": "^7.26.0", 235 "@babel/preset-env": "^7.26.0", 236 "@babel/runtime": "^7.26.0",
+61 -62
src/App.web.tsx
··· 2 import '#/view/icons' 3 import './style.css' 4 5 - import React, {useEffect, useState} from 'react' 6 import {SafeAreaProvider} from 'react-native-safe-area-context' 7 import {msg} from '@lingui/macro' 8 import {useLingui} from '@lingui/react' ··· 81 prefetchLiveEvents() 82 83 function InnerApp() { 84 - const [isReady, setIsReady] = React.useState(false) 85 const {currentAccount} = useSession() 86 const {resumeSession} = useSessionApi() 87 const theme = useColorModeTheme() ··· 116 }) 117 }, [_]) 118 119 - // wait for session to resume 120 - if (!isReady || !hasCheckedReferrer) return <Splash isReady /> 121 - 122 return ( 123 <Alf theme={theme}> 124 <ThemeProvider theme={theme}> 125 <ContextMenuProvider> 126 - <VideoVolumeProvider> 127 - <ActiveVideoProvider> 128 - <React.Fragment 129 - // Resets the entire tree below when it changes: 130 - key={currentAccount?.did}> 131 - <AnalyticsFeaturesContext> 132 - <QueryProvider currentDid={currentAccount?.did}> 133 - <PolicyUpdateOverlayProvider> 134 - <LiveEventsProvider> 135 - <AgeAssuranceV2Provider> 136 - <ComposerProvider> 137 - <MessagesProvider> 138 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 139 - <LabelDefsProvider> 140 - <ModerationOptsProvider> 141 - <LoggedOutViewProvider> 142 - <SelectedFeedProvider> 143 - <HiddenRepliesProvider> 144 - <HomeBadgeProvider> 145 - <UnreadNotifsProvider> 146 - <BackgroundNotificationPreferencesProvider> 147 - <MutedThreadsProvider> 148 - <SafeAreaProvider> 149 - <ProgressGuideProvider> 150 - <ServiceConfigProvider> 151 - <EmailVerificationProvider> 152 - <HideBottomBarBorderProvider> 153 - <IntentDialogProvider> 154 - <Shell /> 155 - <ToastOutlet /> 156 - </IntentDialogProvider> 157 - </HideBottomBarBorderProvider> 158 - </EmailVerificationProvider> 159 - </ServiceConfigProvider> 160 - </ProgressGuideProvider> 161 - </SafeAreaProvider> 162 - </MutedThreadsProvider> 163 - </BackgroundNotificationPreferencesProvider> 164 - </UnreadNotifsProvider> 165 - </HomeBadgeProvider> 166 - </HiddenRepliesProvider> 167 - </SelectedFeedProvider> 168 - </LoggedOutViewProvider> 169 - </ModerationOptsProvider> 170 - </LabelDefsProvider> 171 - </MessagesProvider> 172 - </ComposerProvider> 173 - </AgeAssuranceV2Provider> 174 - </LiveEventsProvider> 175 - </PolicyUpdateOverlayProvider> 176 - </QueryProvider> 177 - </AnalyticsFeaturesContext> 178 - </React.Fragment> 179 - </ActiveVideoProvider> 180 - </VideoVolumeProvider> 181 </ContextMenuProvider> 182 </ThemeProvider> 183 </Alf> ··· 187 function App() { 188 const [isReady, setReady] = useState(false) 189 190 - React.useEffect(() => { 191 Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(() => 192 setReady(true), 193 ) 194 }, []) 195 196 if (!isReady) { 197 - return <Splash isReady /> 198 } 199 200 /*
··· 2 import '#/view/icons' 3 import './style.css' 4 5 + import {Fragment, useEffect, useState} from 'react' 6 import {SafeAreaProvider} from 'react-native-safe-area-context' 7 import {msg} from '@lingui/macro' 8 import {useLingui} from '@lingui/react' ··· 81 prefetchLiveEvents() 82 83 function InnerApp() { 84 + const [isReady, setIsReady] = useState(false) 85 const {currentAccount} = useSession() 86 const {resumeSession} = useSessionApi() 87 const theme = useColorModeTheme() ··· 116 }) 117 }, [_]) 118 119 return ( 120 <Alf theme={theme}> 121 <ThemeProvider theme={theme}> 122 <ContextMenuProvider> 123 + <Splash isReady={isReady && hasCheckedReferrer}> 124 + <VideoVolumeProvider> 125 + <ActiveVideoProvider> 126 + <Fragment 127 + // Resets the entire tree below when it changes: 128 + key={currentAccount?.did}> 129 + <AnalyticsFeaturesContext> 130 + <QueryProvider currentDid={currentAccount?.did}> 131 + <PolicyUpdateOverlayProvider> 132 + <LiveEventsProvider> 133 + <AgeAssuranceV2Provider> 134 + <ComposerProvider> 135 + <MessagesProvider> 136 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 137 + <LabelDefsProvider> 138 + <ModerationOptsProvider> 139 + <LoggedOutViewProvider> 140 + <SelectedFeedProvider> 141 + <HiddenRepliesProvider> 142 + <HomeBadgeProvider> 143 + <UnreadNotifsProvider> 144 + <BackgroundNotificationPreferencesProvider> 145 + <MutedThreadsProvider> 146 + <SafeAreaProvider> 147 + <ProgressGuideProvider> 148 + <ServiceConfigProvider> 149 + <EmailVerificationProvider> 150 + <HideBottomBarBorderProvider> 151 + <IntentDialogProvider> 152 + <Shell /> 153 + <ToastOutlet /> 154 + </IntentDialogProvider> 155 + </HideBottomBarBorderProvider> 156 + </EmailVerificationProvider> 157 + </ServiceConfigProvider> 158 + </ProgressGuideProvider> 159 + </SafeAreaProvider> 160 + </MutedThreadsProvider> 161 + </BackgroundNotificationPreferencesProvider> 162 + </UnreadNotifsProvider> 163 + </HomeBadgeProvider> 164 + </HiddenRepliesProvider> 165 + </SelectedFeedProvider> 166 + </LoggedOutViewProvider> 167 + </ModerationOptsProvider> 168 + </LabelDefsProvider> 169 + </MessagesProvider> 170 + </ComposerProvider> 171 + </AgeAssuranceV2Provider> 172 + </LiveEventsProvider> 173 + </PolicyUpdateOverlayProvider> 174 + </QueryProvider> 175 + </AnalyticsFeaturesContext> 176 + </Fragment> 177 + </ActiveVideoProvider> 178 + </VideoVolumeProvider> 179 + </Splash> 180 </ContextMenuProvider> 181 </ThemeProvider> 182 </Alf> ··· 186 function App() { 187 const [isReady, setReady] = useState(false) 188 189 + useEffect(() => { 190 Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(() => 191 setReady(true), 192 ) 193 }, []) 194 195 if (!isReady) { 196 + return null 197 } 198 199 /*
+82 -14
src/Splash.web.tsx
··· 4 * the app is ready to go. 5 */ 6 7 - import {View} from 'react-native' 8 import Svg, {Path} from 'react-native-svg' 9 10 - import {atoms as a} from '#/alf' 11 12 const size = 125 13 const ratio = 512 / 512 14 15 - export function Splash() { 16 return ( 17 - <View style={[a.fixed, a.inset_0, a.align_center, a.justify_center]}> 18 - <Svg 19 - fill="none" 20 - viewBox="0 0 512 512" 21 - style={[a.relative, {width: size, height: size * ratio, top: -50}]}> 22 - <Path 23 - fill="#ED5345" 24 - d="M374.473 57.7173C367.666 50.7995 357.119 49.1209 348.441 53.1659C347.173 53.7567 342.223 56.0864 334.796 59.8613C326.32 64.1696 314.568 70.3869 301.394 78.0596C275.444 93.1728 242.399 114.83 218.408 139.477C185.983 172.786 158.719 225.503 140.029 267.661C130.506 289.144 122.878 308.661 117.629 322.81C116.301 326.389 115.124 329.63 114.104 332.478C87.1783 336.42 64.534 341.641 47.5078 348.101C37.6493 351.84 28.3222 356.491 21.0573 362.538C13.8818 368.511 6.00003 378.262 6.00003 391.822C6.00014 403.222 11.8738 411.777 17.4566 417.235C23.0009 422.655 29.9593 426.793 36.871 430.062C50.8097 436.653 69.5275 441.988 90.8362 446.249C133.828 454.846 192.21 460 256.001 460C319.79 460 378.172 454.846 421.164 446.249C442.472 441.988 461.19 436.653 475.129 430.062C482.041 426.793 488.999 422.655 494.543 417.235C500.039 411.862 505.817 403.489 505.996 392.353L506 391.822L505.995 391.188C505.754 377.959 498.012 368.417 490.945 362.534C483.679 356.485 474.35 351.835 464.491 348.095C446.749 341.366 422.906 335.982 394.476 331.987C393.6 330.57 392.633 328.995 391.595 327.273C386.477 318.777 379.633 306.842 372.737 293.115C358.503 264.781 345.757 232.098 344.756 206.636C343.87 184.121 351.638 154.087 360.819 127.789C365.27 115.041 369.795 103.877 373.207 95.9072C374.909 91.9309 376.325 88.7712 377.302 86.6328C377.79 85.5645 378.167 84.7524 378.416 84.2224C378.54 83.9579 378.632 83.7635 378.69 83.643C378.718 83.5829 378.739 83.5411 378.75 83.5181C378.753 83.5108 378.756 83.5049 378.757 83.5015C382.909 74.8634 381.196 64.5488 374.473 57.7173Z" 25 - /> 26 - </Svg> 27 - </View> 28 ) 29 }
··· 4 * the app is ready to go. 5 */ 6 7 + import {useEffect, useRef, useState} from 'react' 8 import Svg, {Path} from 'react-native-svg' 9 10 + import {atoms as a, flatten} from '#/alf' 11 12 const size = 125 13 const ratio = 512 / 512 14 15 + export function Splash({ 16 + isReady, 17 + children, 18 + }: React.PropsWithChildren<{ 19 + isReady: boolean 20 + }>) { 21 + const [isAnimationComplete, setIsAnimationComplete] = useState(false) 22 + const splashRef = useRef<HTMLDivElement>(null) 23 + 24 + // hide the static one that's baked into the HTML - gets replaced by our React version below 25 + useEffect(() => { 26 + // double rAF ensures that the React version gets painted first 27 + requestAnimationFrame(() => { 28 + requestAnimationFrame(() => { 29 + const splash = document.getElementById('splash') 30 + if (splash) { 31 + splash.remove() 32 + } 33 + }) 34 + }) 35 + }, []) 36 + 37 + // when ready, we fade/scale out 38 + useEffect(() => { 39 + if (!isReady) return 40 + 41 + const reduceMotion = window.matchMedia( 42 + '(prefers-reduced-motion: reduce)', 43 + ).matches 44 + const node = splashRef.current 45 + if (!node || reduceMotion) { 46 + setIsAnimationComplete(true) 47 + return 48 + } 49 + 50 + const animation = node.animate( 51 + [ 52 + {opacity: 1, transform: 'scale(1)'}, 53 + {opacity: 0, transform: 'scale(1.5)'}, 54 + ], 55 + { 56 + duration: 300, 57 + easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', 58 + fill: 'forwards', 59 + }, 60 + ) 61 + animation.onfinish = () => setIsAnimationComplete(true) 62 + 63 + return () => { 64 + animation.cancel() 65 + } 66 + }, [isReady]) 67 + 68 return ( 69 + <> 70 + {isReady && children} 71 + 72 + {!isAnimationComplete && ( 73 + <div 74 + ref={splashRef} 75 + style={flatten([ 76 + a.fixed, 77 + a.inset_0, 78 + a.flex, 79 + a.align_center, 80 + a.justify_center, 81 + // to compensate for the `top: -50px` below 82 + {transformOrigin: 'center calc(50% - 50px)'}, 83 + ])}> 84 + <Svg 85 + fill="none" 86 + viewBox="0 0 512 512" 87 + style={[a.relative, {width: size, height: size * ratio, top: -50}]}> 88 + <Path 89 + fill="#ED5345" 90 + d="M374.473 57.7173C367.666 50.7995 357.119 49.1209 348.441 53.1659C347.173 53.7567 342.223 56.0864 334.796 59.8613C326.32 64.1696 314.568 70.3869 301.394 78.0596C275.444 93.1728 242.399 114.83 218.408 139.477C185.983 172.786 158.719 225.503 140.029 267.661C130.506 289.144 122.878 308.661 117.629 322.81C116.301 326.389 115.124 329.63 114.104 332.478C87.1783 336.42 64.534 341.641 47.5078 348.101C37.6493 351.84 28.3222 356.491 21.0573 362.538C13.8818 368.511 6.00003 378.262 6.00003 391.822C6.00014 403.222 11.8738 411.777 17.4566 417.235C23.0009 422.655 29.9593 426.793 36.871 430.062C50.8097 436.653 69.5275 441.988 90.8362 446.249C133.828 454.846 192.21 460 256.001 460C319.79 460 378.172 454.846 421.164 446.249C442.472 441.988 461.19 436.653 475.129 430.062C482.041 426.793 488.999 422.655 494.543 417.235C500.039 411.862 505.817 403.489 505.996 392.353L506 391.822L505.995 391.188C505.754 377.959 498.012 368.417 490.945 362.534C483.679 356.485 474.35 351.835 464.491 348.095C446.749 341.366 422.906 335.982 394.476 331.987C393.6 330.57 392.633 328.995 391.595 327.273C386.477 318.777 379.633 306.842 372.737 293.115C358.503 264.781 345.757 232.098 344.756 206.636C343.87 184.121 351.638 154.087 360.819 127.789C365.27 115.041 369.795 103.877 373.207 95.9072C374.909 91.9309 376.325 88.7712 377.302 86.6328C377.79 85.5645 378.167 84.7524 378.416 84.2224C378.54 83.9579 378.632 83.7635 378.69 83.643C378.718 83.5829 378.739 83.5411 378.75 83.5181C378.753 83.5108 378.756 83.5049 378.757 83.5015C382.909 74.8634 381.196 64.5488 374.473 57.7173Z" 91 + /> 92 + </Svg> 93 + </div> 94 + )} 95 + </> 96 ) 97 }
+5
src/analytics/metrics/types.ts
··· 876 'liveEvents:unhideAllFeedBanners': { 877 context: LiveEventFeedMetricContext 878 } 879 }
··· 876 'liveEvents:unhideAllFeedBanners': { 877 context: LiveEventFeedMetricContext 878 } 879 + 880 + 'profile:associated:germ:click-to-chat': {} 881 + 'profile:associated:germ:click-self-info': {} 882 + 'profile:associated:germ:self-disconnect': {} 883 + 'profile:associated:germ:self-reconnect': {} 884 }
+4 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx
··· 59 <BlueskyVideoView 60 url={embed.playlist} 61 autoplay={!autoplayDisabled && !isWithinMessage} 62 - beginMuted={isGif || autoplayDisabled ? false : muted} 63 style={[a.rounded_sm]} 64 onActiveChange={e => { 65 setIsActive(e.nativeEvent.isActive) ··· 68 setIsLoading(e.nativeEvent.isLoading) 69 }} 70 onMutedChange={e => { 71 - setMuted(e.nativeEvent.isMuted) 72 }} 73 onStatusChange={e => { 74 setStatus(e.nativeEvent.status)
··· 59 <BlueskyVideoView 60 url={embed.playlist} 61 autoplay={!autoplayDisabled && !isWithinMessage} 62 + beginMuted={isGif || (autoplayDisabled ? false : muted)} 63 style={[a.rounded_sm]} 64 onActiveChange={e => { 65 setIsActive(e.nativeEvent.isActive) ··· 68 setIsLoading(e.nativeEvent.isLoading) 69 }} 70 onMutedChange={e => { 71 + if (!isGif) { 72 + setMuted(e.nativeEvent.isMuted) 73 + } 74 }} 75 onStatusChange={e => { 76 setStatus(e.nativeEvent.status)
+79 -63
src/components/ProgressGuide/List.tsx
··· 1 - import {type StyleProp, View, type ViewStyle} from 'react-native' 2 import {msg, Trans} from '@lingui/macro' 3 import {useLingui} from '@lingui/react' 4 ··· 11 import {UserAvatar} from '#/view/com/util/UserAvatar' 12 import {atoms as a, useBreakpoints, useLayoutBreakpoints, useTheme} from '#/alf' 13 import {Button, ButtonIcon} from '#/components/Button' 14 - import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' 15 import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' 16 import {Text} from '#/components/Typography' 17 import type * as bsky from '#/types/bsky' ··· 51 a.flex_col, 52 a.gap_md, 53 a.rounded_md, 54 - t.atoms.bg_contrast_25, 55 a.p_lg, 56 style, 57 ]}> ··· 81 a.justify_between, 82 a.gap_sm, 83 ] 84 - : a.flex_col, 85 - !inlineLayout && a.gap_md, 86 ]}> 87 <StackedAvatars follows={follows?.pages?.[0]?.follows} /> 88 <FollowDialog guide={guide} showArrow={inlineLayout} /> ··· 112 113 function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) { 114 const t = useTheme() 115 - const {centerColumnOffset} = useLayoutBreakpoints() 116 117 - // Smaller avatars for narrower viewport 118 - const avatarSize = centerColumnOffset ? 30 : 37 119 - const overlap = centerColumnOffset ? 9 : 11 120 - const iconSize = centerColumnOffset ? 14 : 18 121 122 - // Use actual follows count, not the guide's event counter 123 const followedAvatars = follows?.slice(0, TOTAL_AVATARS) ?? [] 124 const remainingSlots = TOTAL_AVATARS - followedAvatars.length 125 126 - // Total width calculation: first avatar + (remaining * visible portion) 127 - const totalWidth = avatarSize + (TOTAL_AVATARS - 1) * (avatarSize - overlap) 128 - 129 return ( 130 - <View style={[a.flex_row, a.self_start, {width: totalWidth}]}> 131 - {/* Show followed user avatars */} 132 - {followedAvatars.map((follow, i) => ( 133 - <View 134 - key={follow.did} 135 - style={[ 136 - a.rounded_full, 137 - { 138 - marginLeft: i === 0 ? 0 : -overlap, 139 - zIndex: TOTAL_AVATARS - i, 140 - borderWidth: 2, 141 - borderColor: t.atoms.bg_contrast_25.backgroundColor, 142 - }, 143 - ]}> 144 - <UserAvatar 145 - type="user" 146 - size={avatarSize - 4} 147 - avatar={follow.avatar} 148 - /> 149 - </View> 150 - ))} 151 - {/* Show placeholder avatars for remaining slots */} 152 - {Array(remainingSlots) 153 - .fill(0) 154 - .map((_, i) => ( 155 - <View 156 - key={`placeholder-${i}`} 157 - style={[ 158 - a.align_center, 159 - a.justify_center, 160 - a.rounded_full, 161 - t.atoms.bg_contrast_100, 162 - { 163 - width: avatarSize, 164 - height: avatarSize, 165 - marginLeft: 166 - followedAvatars.length === 0 && i === 0 ? 0 : -overlap, 167 - zIndex: TOTAL_AVATARS - followedAvatars.length - i, 168 - borderWidth: 2, 169 - borderColor: t.atoms.bg_contrast_25.backgroundColor, 170 - }, 171 - ]}> 172 - <PersonIcon 173 - width={iconSize} 174 - height={iconSize} 175 - fill={t.atoms.text_contrast_low.color} 176 - /> 177 - </View> 178 - ))} 179 </View> 180 ) 181 }
··· 1 + import {useState} from 'react' 2 + import { 3 + type LayoutChangeEvent, 4 + type StyleProp, 5 + View, 6 + type ViewStyle, 7 + } from 'react-native' 8 import {msg, Trans} from '@lingui/macro' 9 import {useLingui} from '@lingui/react' 10 ··· 17 import {UserAvatar} from '#/view/com/util/UserAvatar' 18 import {atoms as a, useBreakpoints, useLayoutBreakpoints, useTheme} from '#/alf' 19 import {Button, ButtonIcon} from '#/components/Button' 20 + import {Person_Filled_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' 21 import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' 22 import {Text} from '#/components/Typography' 23 import type * as bsky from '#/types/bsky' ··· 57 a.flex_col, 58 a.gap_md, 59 a.rounded_md, 60 + t.atoms.bg_contrast_50, 61 a.p_lg, 62 style, 63 ]}> ··· 87 a.justify_between, 88 a.gap_sm, 89 ] 90 + : [a.flex_col, a.gap_md], 91 ]}> 92 <StackedAvatars follows={follows?.pages?.[0]?.follows} /> 93 <FollowDialog guide={guide} showArrow={inlineLayout} /> ··· 117 118 function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) { 119 const t = useTheme() 120 + const [containerWidth, setContainerWidth] = useState(0) 121 122 + const onLayout = (e: LayoutChangeEvent) => { 123 + setContainerWidth(e.nativeEvent.layout.width) 124 + } 125 126 + // Overlap ratio (22% of avatar size) 127 + const overlapRatio = 0.22 128 + 129 + // Calculate avatar size to fill container width 130 + // Formula: containerWidth = avatarSize * count - overlap * (count - 1) 131 + // Where overlap = avatarSize * overlapRatio 132 + const visiblePortions = TOTAL_AVATARS - overlapRatio * (TOTAL_AVATARS - 1) 133 + const avatarSize = containerWidth > 0 ? containerWidth / visiblePortions : 0 134 + const overlap = avatarSize * overlapRatio 135 + const iconSize = avatarSize * 0.5 136 + 137 const followedAvatars = follows?.slice(0, TOTAL_AVATARS) ?? [] 138 const remainingSlots = TOTAL_AVATARS - followedAvatars.length 139 140 return ( 141 + <View style={[a.flex_row, a.flex_1]} onLayout={onLayout}> 142 + {containerWidth > 0 && ( 143 + <> 144 + {/* Show followed user avatars */} 145 + {followedAvatars.map((follow, i) => ( 146 + <View 147 + key={follow.did} 148 + style={[ 149 + a.rounded_full, 150 + a.border, 151 + t.atoms.border_contrast_low, 152 + { 153 + marginLeft: i === 0 ? 0 : -overlap, 154 + zIndex: TOTAL_AVATARS - i, 155 + }, 156 + ]}> 157 + <UserAvatar 158 + type="user" 159 + size={avatarSize - 2} 160 + avatar={follow.avatar} 161 + noBorder 162 + /> 163 + </View> 164 + ))} 165 + {/* Show placeholder avatars for remaining slots */} 166 + {Array(remainingSlots) 167 + .fill(0) 168 + .map((_, i) => ( 169 + <View 170 + key={`placeholder-${i}`} 171 + style={[ 172 + a.align_center, 173 + a.justify_center, 174 + a.rounded_full, 175 + t.atoms.bg_contrast_300, 176 + a.border, 177 + t.atoms.border_contrast_low, 178 + { 179 + width: avatarSize, 180 + height: avatarSize, 181 + marginLeft: 182 + followedAvatars.length === 0 && i === 0 ? 0 : -overlap, 183 + zIndex: TOTAL_AVATARS - followedAvatars.length - i, 184 + }, 185 + ]}> 186 + <PersonIcon 187 + width={iconSize} 188 + height={iconSize} 189 + fill={t.atoms.bg_contrast_50.backgroundColor} 190 + /> 191 + </View> 192 + ))} 193 + </> 194 + )} 195 </View> 196 ) 197 }
+17 -2
src/components/dialogs/LinkWarning.tsx
··· 22 webOptions={{alignCenter: true}} 23 onClose={linkWarningDialogControl.clear}> 24 <Dialog.Handle /> 25 - <InAppBrowserConsentInner link={linkWarningDialogControl.value} /> 26 </Dialog.Outer> 27 ) 28 } 29 30 - function InAppBrowserConsentInner({ 31 link, 32 }: { 33 link?: {href: string; displayText: string; share?: boolean}
··· 22 webOptions={{alignCenter: true}} 23 onClose={linkWarningDialogControl.clear}> 24 <Dialog.Handle /> 25 + <LinkWarningDialogInner link={linkWarningDialogControl.value} /> 26 + </Dialog.Outer> 27 + ) 28 + } 29 + 30 + export function CustomLinkWarningDialog({ 31 + control, 32 + link, 33 + }: { 34 + control: Dialog.DialogControlProps 35 + link?: {href: string; displayText: string; share?: boolean} 36 + }) { 37 + return ( 38 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 39 + <Dialog.Handle /> 40 + <LinkWarningDialogInner link={link} /> 41 </Dialog.Outer> 42 ) 43 } 44 45 + function LinkWarningDialogInner({ 46 link, 47 }: { 48 link?: {href: string; displayText: string; share?: boolean}
+56 -58
src/components/dialogs/StarterPackDialog.tsx
··· 1 - import {useCallback, useState} from 'react' 2 import {View} from 'react-native' 3 import { 4 type AppBskyGraphGetStarterPacksWithMembership, ··· 7 import {msg, Plural, Trans} from '@lingui/macro' 8 import {useLingui} from '@lingui/react' 9 import {useNavigation} from '@react-navigation/native' 10 - import {useQueryClient} from '@tanstack/react-query' 11 12 import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' 13 import {type NavigationProp} from '#/lib/routes/types' 14 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 15 - import { 16 - invalidateActorStarterPacksWithMembershipQuery, 17 - useActorStarterPacksWithMembershipsQuery, 18 - } from '#/state/queries/actor-starter-packs' 19 import { 20 useListMembershipAddMutation, 21 useListMembershipRemoveMutation, 22 } from '#/state/queries/list-memberships' 23 - import * as Toast from '#/view/com/util/Toast' 24 - import {atoms as a, useTheme} from '#/alf' 25 import {AvatarStack} from '#/components/AvatarStack' 26 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 27 import * as Dialog from '#/components/Dialog' ··· 30 import {StarterPack} from '#/components/icons/StarterPack' 31 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 32 import {Loader} from '#/components/Loader' 33 import {Text} from '#/components/Typography' 34 import {useAnalytics} from '#/analytics' 35 import {IS_WEB} from '#/env' ··· 132 }) { 133 const control = Dialog.useDialogContext() 134 const {_} = useLingui() 135 136 const enableSquareButtons = useEnableSquareButtons() 137 ··· 158 159 const renderItem = useCallback( 160 ({item}: {item: StarterPackWithMembership}) => ( 161 - <StarterPackItem starterPackWithMembership={item} targetDid={targetDid} /> 162 ), 163 - [targetDid], 164 ) 165 166 const onClose = useCallback(() => { ··· 171 <> 172 <View 173 style={[ 174 - {justifyContent: 'space-between', flexDirection: 'row'}, 175 - IS_WEB ? a.mb_2xl : a.my_lg, 176 a.align_center, 177 ]}> 178 <Text style={[a.text_lg, a.font_semi_bold]}> 179 <Trans>Add to starter packs</Trans> ··· 184 variant="ghost" 185 color="secondary" 186 size="small" 187 - shape={enableSquareButtons ? 'square' : 'round'}> 188 <ButtonIcon icon={XIcon} /> 189 </Button> 190 </View> ··· 235 onEndReachedThreshold={0.1} 236 ListHeaderComponent={listHeader} 237 ListEmptyComponent={<Empty onStartWizard={onStartWizard} />} 238 - style={IS_WEB ? [a.px_md, {minHeight: 500}] : [a.px_2xl, a.pt_lg]} 239 /> 240 ) 241 } ··· 243 function StarterPackItem({ 244 starterPackWithMembership, 245 targetDid, 246 }: { 247 starterPackWithMembership: StarterPackWithMembership 248 targetDid: string 249 }) { 250 const t = useTheme() 251 const ax = useAnalytics() 252 const {_} = useLingui() 253 - const queryClient = useQueryClient() 254 255 const starterPack = starterPackWithMembership.starterPack 256 const isInPack = !!starterPackWithMembership.listItem 257 258 - const [isPendingRefresh, setIsPendingRefresh] = useState(false) 259 260 - const {mutate: addMembership} = useListMembershipAddMutation({ 261 - onSuccess: () => { 262 - Toast.show(_(msg`Added to starter pack`)) 263 - // Use a timeout to wait for the appview to update, matching the pattern 264 - // in list-memberships.ts 265 - setTimeout(() => { 266 - invalidateActorStarterPacksWithMembershipQuery({ 267 - queryClient, 268 - did: targetDid, 269 - }) 270 - setIsPendingRefresh(false) 271 - }, 1e3) 272 - }, 273 - onError: () => { 274 - Toast.show(_(msg`Failed to add to starter pack`), 'xmark') 275 - setIsPendingRefresh(false) 276 - }, 277 - }) 278 279 - const {mutate: removeMembership} = useListMembershipRemoveMutation({ 280 - onSuccess: () => { 281 - Toast.show(_(msg`Removed from starter pack`)) 282 - // Use a timeout to wait for the appview to update, matching the pattern 283 - // in list-memberships.ts 284 - setTimeout(() => { 285 - invalidateActorStarterPacksWithMembershipQuery({ 286 - queryClient, 287 - did: targetDid, 288 - }) 289 - setIsPendingRefresh(false) 290 - }, 1e3) 291 - }, 292 - onError: () => { 293 - Toast.show(_(msg`Failed to remove from starter pack`), 'xmark') 294 - setIsPendingRefresh(false) 295 - }, 296 - }) 297 298 const handleToggleMembership = () => { 299 - if (!starterPack.list?.uri || isPendingRefresh) return 300 301 const listUri = starterPack.list.uri 302 const starterPackUri = starterPack.uri 303 304 - setIsPendingRefresh(true) 305 - 306 if (!isInPack) { 307 addMembership({ 308 listUri: listUri, ··· 312 } else { 313 if (!starterPackWithMembership.listItem?.uri) { 314 console.error('Cannot remove: missing membership URI') 315 - setIsPendingRefresh(false) 316 return 317 } 318 removeMembership({ ··· 347 starterPack.listItemsSample.length > 0 && ( 348 <> 349 <AvatarStack 350 - size={32} 351 profiles={starterPack.listItemsSample 352 ?.slice(0, 4) 353 .map(p => p.subject)} ··· 378 label={isInPack ? _(msg`Remove`) : _(msg`Add`)} 379 color={isInPack ? 'secondary' : 'primary_subtle'} 380 size="tiny" 381 - disabled={isPendingRefresh} 382 onPress={handleToggleMembership}> 383 <ButtonText> 384 {isInPack ? <Trans>Remove</Trans> : <Trans>Add</Trans>} 385 </ButtonText>
··· 1 + import {useCallback} from 'react' 2 import {View} from 'react-native' 3 import { 4 type AppBskyGraphGetStarterPacksWithMembership, ··· 7 import {msg, Plural, Trans} from '@lingui/macro' 8 import {useLingui} from '@lingui/react' 9 import {useNavigation} from '@react-navigation/native' 10 11 import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' 12 import {type NavigationProp} from '#/lib/routes/types' 13 + import {isNetworkError} from '#/lib/strings/errors' 14 + import {logger} from '#/logger' 15 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 16 + import {useActorStarterPacksWithMembershipsQuery} from '#/state/queries/actor-starter-packs' 17 import { 18 useListMembershipAddMutation, 19 useListMembershipRemoveMutation, 20 } from '#/state/queries/list-memberships' 21 + import {useProfileQuery} from '#/state/queries/profile' 22 + import {atoms as a, native, platform, useTheme} from '#/alf' 23 import {AvatarStack} from '#/components/AvatarStack' 24 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 25 import * as Dialog from '#/components/Dialog' ··· 28 import {StarterPack} from '#/components/icons/StarterPack' 29 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 30 import {Loader} from '#/components/Loader' 31 + import * as Toast from '#/components/Toast' 32 import {Text} from '#/components/Typography' 33 import {useAnalytics} from '#/analytics' 34 import {IS_WEB} from '#/env' ··· 131 }) { 132 const control = Dialog.useDialogContext() 133 const {_} = useLingui() 134 + const {data: subject} = useProfileQuery({did: targetDid}) 135 136 const enableSquareButtons = useEnableSquareButtons() 137 ··· 158 159 const renderItem = useCallback( 160 ({item}: {item: StarterPackWithMembership}) => ( 161 + <StarterPackItem 162 + starterPackWithMembership={item} 163 + targetDid={targetDid} 164 + subject={subject} 165 + /> 166 ), 167 + [targetDid, subject], 168 ) 169 170 const onClose = useCallback(() => { ··· 175 <> 176 <View 177 style={[ 178 + a.justify_between, 179 a.align_center, 180 + a.flex_row, 181 + a.pb_lg, 182 + native(a.pt_lg), 183 ]}> 184 <Text style={[a.text_lg, a.font_semi_bold]}> 185 <Trans>Add to starter packs</Trans> ··· 190 variant="ghost" 191 color="secondary" 192 size="small" 193 + shape={enableSquareButtons ? 'square' : 'round'} 194 + style={{margin: -8}}> 195 <ButtonIcon icon={XIcon} /> 196 </Button> 197 </View> ··· 242 onEndReachedThreshold={0.1} 243 ListHeaderComponent={listHeader} 244 ListEmptyComponent={<Empty onStartWizard={onStartWizard} />} 245 + style={platform({ 246 + web: [a.px_2xl, {minHeight: 500}], 247 + native: [a.px_2xl, a.pt_lg], 248 + })} 249 /> 250 ) 251 } ··· 253 function StarterPackItem({ 254 starterPackWithMembership, 255 targetDid, 256 + subject, 257 }: { 258 starterPackWithMembership: StarterPackWithMembership 259 targetDid: string 260 + subject?: bsky.profile.AnyProfileView 261 }) { 262 const t = useTheme() 263 const ax = useAnalytics() 264 const {_} = useLingui() 265 266 const starterPack = starterPackWithMembership.starterPack 267 const isInPack = !!starterPackWithMembership.listItem 268 269 + const {mutate: addMembership, isPending: isPendingAdd} = 270 + useListMembershipAddMutation({ 271 + subject, 272 + onSuccess: () => { 273 + Toast.show(_(msg`Added to starter pack`)) 274 + }, 275 + onError: err => { 276 + if (!isNetworkError(err)) { 277 + logger.error('Failed to add to starter pack', {safeMessage: err}) 278 + } 279 + Toast.show(_(msg`Failed to add to starter pack`), {type: 'error'}) 280 + }, 281 + }) 282 283 + const {mutate: removeMembership, isPending: isPendingRemove} = 284 + useListMembershipRemoveMutation({ 285 + onSuccess: () => { 286 + Toast.show(_(msg`Removed from starter pack`)) 287 + }, 288 + onError: err => { 289 + if (!isNetworkError(err)) { 290 + logger.error('Failed to remove from starter pack', {safeMessage: err}) 291 + } 292 + Toast.show(_(msg`Failed to remove from starter pack`), {type: 'error'}) 293 + }, 294 + }) 295 296 + const isPending = isPendingAdd || isPendingRemove 297 298 const handleToggleMembership = () => { 299 + if (!starterPack.list?.uri || isPending) return 300 301 const listUri = starterPack.list.uri 302 const starterPackUri = starterPack.uri 303 304 if (!isInPack) { 305 addMembership({ 306 listUri: listUri, ··· 310 } else { 311 if (!starterPackWithMembership.listItem?.uri) { 312 console.error('Cannot remove: missing membership URI') 313 return 314 } 315 removeMembership({ ··· 344 starterPack.listItemsSample.length > 0 && ( 345 <> 346 <AvatarStack 347 + size={24} 348 profiles={starterPack.listItemsSample 349 ?.slice(0, 4) 350 .map(p => p.subject)} ··· 375 label={isInPack ? _(msg`Remove`) : _(msg`Add`)} 376 color={isInPack ? 'secondary' : 'primary_subtle'} 377 size="tiny" 378 + disabled={isPending} 379 onPress={handleToggleMembership}> 380 + {isPending && <ButtonIcon icon={Loader} />} 381 <ButtonText> 382 {isInPack ? <Trans>Remove</Trans> : <Trans>Add</Trans>} 383 </ButtonText>
+4
src/components/icons/Person.tsx
··· 36 export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({ 37 path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z', 38 })
··· 36 export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({ 37 path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z', 38 }) 39 + 40 + export const Person_Filled_Corner2_Rounded = createSinglePathSVG({ 41 + path: 'M12.233 2a4.433 4.433 0 1 0 0 8.867 4.433 4.433 0 0 0 0-8.867ZM12.233 12.133c-3.888 0-6.863 2.263-8.071 5.435-.346.906-.11 1.8.44 2.436.535.619 1.36.996 2.25.996h10.762c.89 0 1.716-.377 2.25-.996.55-.636.786-1.53.441-2.436-1.208-3.173-4.184-5.435-8.072-5.435Z', 42 + })
+2
src/lib/constants.ts
··· 51 52 export const MAX_GRAPHEME_LENGTH = 300 53 54 export const MAX_DM_GRAPHEME_LENGTH = 1000 55 56 // Recommended is 100 per: https://www.w3.org/WAI/GL/WCAG20/tests/test3.html
··· 51 52 export const MAX_GRAPHEME_LENGTH = 300 53 54 + export const MAX_DRAFT_GRAPHEME_LENGTH = 1000 55 + 56 export const MAX_DM_GRAPHEME_LENGTH = 1000 57 58 // Recommended is 100 per: https://www.w3.org/WAI/GL/WCAG20/tests/test3.html
+12
src/lib/strings/errors.ts
··· 30 if (str.includes('Bad token scope') || str.includes('Bad token method')) { 31 return t`This feature is not available while using an App Password. Please sign in with your main password.` 32 } 33 if (str.startsWith('Error: ')) { 34 return str.slice('Error: '.length) 35 }
··· 30 if (str.includes('Bad token scope') || str.includes('Bad token method')) { 31 return t`This feature is not available while using an App Password. Please sign in with your main password.` 32 } 33 + if (str.includes('Account has been suspended')) { 34 + return t`Account has been suspended` 35 + } 36 + if (str.includes('Account is deactivated')) { 37 + return t`Account is deactivated` 38 + } 39 + if (str.includes('Profile not found')) { 40 + return t`Profile not found` 41 + } 42 + if (str.includes('Unable to resolve handle')) { 43 + return t`Unable to resolve handle` 44 + } 45 if (str.startsWith('Error: ')) { 46 return str.slice('Error: '.length) 47 }
+184 -118
src/locale/locales/en/messages.po
··· 132 msgid "{0, plural, other {# people have}} joined Bluesky via this starter pack!" 133 msgstr "" 134 135 - #: src/components/dialogs/StarterPackDialog.tsx:361 136 msgid "{0, plural, other {+# more}}" 137 msgstr "" 138 ··· 643 msgid "Account followed" 644 msgstr "" 645 646 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:446 647 #: src/view/com/profile/ProfileMenu.tsx:158 648 msgctxt "toast" ··· 670 msgid "Account removed from quick access" 671 msgstr "" 672 673 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:84 674 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:290 675 #: src/view/com/profile/ProfileMenu.tsx:172 676 msgctxt "toast" 677 msgid "Account unblocked" ··· 704 705 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:169 706 #: src/components/dialogs/MutedWords.tsx:333 707 - #: src/components/dialogs/StarterPackDialog.tsx:375 708 - #: src/components/dialogs/StarterPackDialog.tsx:381 709 #: src/view/com/modals/UserAddRemoveLists.tsx:235 710 msgid "Add" 711 msgstr "" ··· 835 msgid "Add this feed to your feeds" 836 msgstr "" 837 838 - #: src/view/com/profile/ProfileMenu.tsx:343 839 - #: src/view/com/profile/ProfileMenu.tsx:346 840 msgid "Add to lists" 841 msgstr "" 842 ··· 844 msgid "Add to saved posts" 845 msgstr "" 846 847 - #: src/components/dialogs/StarterPackDialog.tsx:176 848 - #: src/view/com/profile/ProfileMenu.tsx:334 849 - #: src/view/com/profile/ProfileMenu.tsx:337 850 msgid "Add to starter packs" 851 msgstr "" 852 ··· 863 msgid "Added to list" 864 msgstr "" 865 866 - #: src/components/dialogs/StarterPackDialog.tsx:259 867 msgid "Added to starter pack" 868 msgstr "" 869 ··· 1435 msgid "Before creating a post or replying, you must first verify your email." 1436 msgstr "" 1437 1438 - #: src/components/dialogs/StarterPackDialog.tsx:71 1439 #: src/components/StarterPack/ProfileStarterPacks.tsx:263 1440 #: src/components/StarterPack/ProfileStarterPacks.tsx:273 1441 #: src/view/screens/Profile.tsx:351 ··· 1482 msgstr "" 1483 1484 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:821 1485 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 1486 - #: src/view/com/profile/ProfileMenu.tsx:551 1487 msgid "Block" 1488 msgstr "" 1489 ··· 1493 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:708 1494 #: src/screens/Messages/components/RequestButtons.tsx:144 1495 #: src/screens/Messages/components/RequestButtons.tsx:146 1496 - #: src/view/com/profile/ProfileMenu.tsx:457 1497 - #: src/view/com/profile/ProfileMenu.tsx:464 1498 msgid "Block account" 1499 msgstr "" 1500 1501 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:816 1502 - #: src/view/com/profile/ProfileMenu.tsx:534 1503 msgid "Block Account?" 1504 msgstr "" 1505 ··· 1551 msgstr "" 1552 1553 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:818 1554 - #: src/view/com/profile/ProfileMenu.tsx:546 1555 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1556 msgstr "" 1557 ··· 1567 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1568 msgstr "" 1569 1570 - #: src/view/com/profile/ProfileMenu.tsx:543 1571 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." 1572 msgstr "" 1573 ··· 1601 msgid "Bluesky is an open network where you can choose your own provider. If you're new here, we recommend sticking with the default Bluesky Social option." 1602 msgstr "" 1603 1604 - #: src/components/ProgressGuide/List.tsx:103 1605 msgid "Bluesky is better with friends!" 1606 msgstr "" 1607 ··· 2104 #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:190 2105 #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:198 2106 #: src/components/dialogs/SearchablePeopleList.tsx:295 2107 - #: src/components/dialogs/StarterPackDialog.tsx:179 2108 #: src/components/dms/AfterReportDialog.tsx:93 2109 #: src/components/dms/AfterReportDialog.tsx:98 2110 #: src/components/dms/AfterReportDialog.tsx:208 ··· 2477 msgid "Copy App Password" 2478 msgstr "" 2479 2480 - #: src/view/com/profile/ProfileMenu.tsx:494 2481 - #: src/view/com/profile/ProfileMenu.tsx:497 2482 msgid "Copy at:// URI" 2483 msgstr "" 2484 ··· 2493 msgstr "" 2494 2495 #: src/screens/Settings/components/ChangeHandleDialog.tsx:502 2496 - #: src/view/com/profile/ProfileMenu.tsx:503 2497 - #: src/view/com/profile/ProfileMenu.tsx:506 2498 msgid "Copy DID" 2499 msgstr "" 2500 ··· 2594 msgstr "" 2595 2596 #: src/screens/ProfileList/components/ErrorScreen.tsx:26 2597 - #: src/screens/ProfileList/index.tsx:79 2598 - #: src/screens/ProfileList/index.tsx:101 2599 msgid "Could not load list" 2600 msgstr "" 2601 ··· 2629 2630 #. Text on button to create a new starter pack 2631 #. Text on button to create a new starter pack 2632 - #: src/components/dialogs/StarterPackDialog.tsx:112 2633 - #: src/components/dialogs/StarterPackDialog.tsx:201 2634 #: src/components/StarterPack/ProfileStarterPacks.tsx:328 2635 msgid "Create" 2636 msgstr "" ··· 2711 msgid "Create report for {0}" 2712 msgstr "" 2713 2714 - #: src/components/dialogs/StarterPackDialog.tsx:107 2715 - #: src/components/dialogs/StarterPackDialog.tsx:196 2716 msgid "Create starter pack" 2717 msgstr "" 2718 ··· 3025 msgid "Discard post?" 3026 msgstr "" 3027 3028 #: src/screens/Settings/components/PwiOptOut.tsx:87 3029 #: src/screens/Settings/components/PwiOptOut.tsx:91 3030 msgid "Discourage apps from showing my account to logged-out users" ··· 3052 msgid "Dismiss error" 3053 msgstr "" 3054 3055 - #: src/components/ProgressGuide/List.tsx:67 3056 msgid "Dismiss getting started guide" 3057 msgstr "" 3058 ··· 3279 msgid "Edit list details" 3280 msgstr "" 3281 3282 - #: src/view/com/profile/ProfileMenu.tsx:357 3283 - #: src/view/com/profile/ProfileMenu.tsx:377 3284 msgid "Edit live status" 3285 msgstr "" 3286 ··· 3309 #: src/screens/Profile/Header/EditProfileDialog.tsx:268 3310 #: src/screens/Profile/Header/EditProfileDialog.tsx:274 3311 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:295 3312 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:324 3313 msgid "Edit profile" 3314 msgstr "" 3315 3316 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:298 3317 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:326 3318 msgid "Edit Profile" 3319 msgstr "" 3320 ··· 3524 msgid "Enter your username and password" 3525 msgstr "" 3526 3527 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:146 3528 msgid "Enters full screen" 3529 msgstr "" 3530 ··· 3710 msgid "Failed to add emoji reaction" 3711 msgstr "" 3712 3713 - #: src/components/dialogs/StarterPackDialog.tsx:271 3714 msgid "Failed to add to starter pack" 3715 msgstr "" 3716 ··· 3747 3748 #: src/screens/StarterPack/StarterPackScreen.tsx:732 3749 msgid "Failed to delete starter pack" 3750 msgstr "" 3751 3752 #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:133 ··· 3823 msgid "Failed to pin post" 3824 msgstr "" 3825 3826 #: src/screens/Settings/FindContactsSettings.tsx:487 3827 msgid "Failed to remove data due to a network error, please check your internet connection." 3828 msgstr "" ··· 3836 msgid "Failed to remove emoji reaction" 3837 msgstr "" 3838 3839 - #: src/components/dialogs/StarterPackDialog.tsx:290 3840 msgid "Failed to remove from starter pack" 3841 msgstr "" 3842 ··· 4126 #: src/components/ProfileHoverCard/index.web.tsx:497 4127 #: src/components/ProfileHoverCard/index.web.tsx:508 4128 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:152 4129 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:381 4130 #: src/screens/VideoFeed/index.tsx:874 4131 #: src/view/com/notifications/NotificationFeedItem.tsx:841 4132 #: src/view/com/notifications/NotificationFeedItem.tsx:848 ··· 4134 msgstr "" 4135 4136 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:139 4137 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:369 4138 msgid "Follow {0}" 4139 msgstr "" 4140 ··· 4146 msgid "Follow 10 accounts" 4147 msgstr "" 4148 4149 - #: src/components/ProgressGuide/List.tsx:60 4150 msgid "Follow 10 people to get started" 4151 msgstr "" 4152 4153 - #: src/components/ProgressGuide/List.tsx:102 4154 msgid "Follow 7 accounts" 4155 msgstr "" 4156 ··· 4178 #. User is not following this account, click to follow back 4179 #: src/components/ProfileCard.tsx:553 4180 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:150 4181 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:379 4182 #: src/view/com/notifications/NotificationFeedItem.tsx:841 4183 #: src/view/com/notifications/NotificationFeedItem.tsx:848 4184 msgid "Follow back" ··· 4223 #: src/components/ProfileHoverCard/index.web.tsx:496 4224 #: src/components/ProfileHoverCard/index.web.tsx:507 4225 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:155 4226 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:377 4227 #: src/screens/VideoFeed/index.tsx:872 4228 #: src/view/com/notifications/NotificationFeedItem.tsx:819 4229 #: src/view/com/notifications/NotificationFeedItem.tsx:836 ··· 4237 msgstr "" 4238 4239 #: src/components/ProfileCard.tsx:509 4240 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:244 4241 #: src/view/com/notifications/NotificationFeedItem.tsx:772 4242 msgid "Following {0}" 4243 msgstr "" ··· 4339 msgid "Generate a starter pack" 4340 msgstr "" 4341 4342 #: src/view/shell/Drawer.tsx:374 4343 msgid "Get help" 4344 msgstr "" ··· 4421 msgid "Glorification of violence" 4422 msgstr "" 4423 4424 - #: src/components/dialogs/LinkWarning.tsx:111 4425 - #: src/components/dialogs/LinkWarning.tsx:117 4426 #: src/components/Layout/Header/index.tsx:128 4427 #: src/components/moderation/ScreenHider.tsx:160 4428 #: src/components/moderation/ScreenHider.tsx:169 ··· 4471 msgid "Go Home" 4472 msgstr "" 4473 4474 - #: src/view/com/profile/ProfileMenu.tsx:358 4475 - #: src/view/com/profile/ProfileMenu.tsx:379 4476 msgid "Go live" 4477 msgstr "" 4478 ··· 4483 msgid "Go Live" 4484 msgstr "" 4485 4486 - #: src/view/com/profile/ProfileMenu.tsx:355 4487 - #: src/view/com/profile/ProfileMenu.tsx:375 4488 msgid "Go live (disabled)" 4489 msgstr "" 4490 ··· 4527 4528 #: src/components/live/GoLiveDisabledDialog.tsx:104 4529 msgid "Going live is currently disabled for your account" 4530 msgstr "" 4531 4532 #: src/lib/moderation/useGlobalLabelStrings.ts:46 ··· 5284 msgid "Learn more about what is public on Bluesky." 5285 msgstr "" 5286 5287 #: src/components/ageAssurance/AgeAssuranceAdmonition.tsx:89 5288 msgid "Learn more in your <0>account settings.</0>" 5289 msgstr "" ··· 5314 msgid "Leave them all unselected to see any language." 5315 msgstr "" 5316 5317 - #: src/components/dialogs/LinkWarning.tsx:67 5318 - #: src/components/dialogs/LinkWarning.tsx:75 5319 msgid "Leaving Bluesky" 5320 msgstr "" 5321 ··· 5356 msgid "Like ({0, plural, one {# like} other {# likes}})" 5357 msgstr "" 5358 5359 - #: src/components/ProgressGuide/List.tsx:96 5360 msgid "Like 10 posts" 5361 msgstr "" 5362 ··· 5473 msgid "List has been hidden" 5474 msgstr "" 5475 5476 - #: src/screens/ProfileList/index.tsx:172 5477 msgid "List Hidden" 5478 msgstr "" 5479 ··· 5643 msgid "Make one for me" 5644 msgstr "" 5645 5646 - #: src/components/dialogs/LinkWarning.tsx:85 5647 msgid "Make sure this is where you intend to go!" 5648 msgstr "" 5649 ··· 5870 msgid "Music" 5871 msgstr "" 5872 5873 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:167 5874 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:97 5875 msgctxt "video" 5876 msgid "Mute" ··· 5883 5884 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:689 5885 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:695 5886 - #: src/view/com/profile/ProfileMenu.tsx:436 5887 - #: src/view/com/profile/ProfileMenu.tsx:443 5888 msgid "Mute account" 5889 msgstr "" 5890 ··· 6014 msgstr "" 6015 6016 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:193 6017 - #: src/view/com/profile/ProfileMenu.tsx:391 6018 msgid "New" 6019 msgstr "" 6020 ··· 6081 msgstr "" 6082 6083 #: src/screens/Profile/ProfileFeed/index.tsx:250 6084 - #: src/screens/ProfileList/index.tsx:246 6085 - #: src/screens/ProfileList/index.tsx:284 6086 #: src/view/screens/Feeds.tsx:552 6087 #: src/view/screens/Notifications.tsx:166 6088 #: src/view/screens/Profile.tsx:594 ··· 6107 msgid "New posts from {firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}}" 6108 msgstr "" 6109 6110 - #: src/components/dialogs/StarterPackDialog.tsx:193 6111 msgid "New starter pack" 6112 msgstr "" 6113 ··· 6211 msgstr "" 6212 6213 #: src/components/ProfileCard.tsx:531 6214 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:269 6215 #: src/view/com/notifications/NotificationFeedItem.tsx:792 6216 msgid "No longer following {0}" 6217 msgstr "" ··· 6373 msgid "Not ready to hit post? Keep your best ideas in Drafts until the timing is just right." 6374 msgstr "" 6375 6376 - #: src/view/com/profile/ProfileMenu.tsx:558 6377 msgid "Note about sharing" 6378 msgstr "" 6379 ··· 6586 msgid "Open full emoji list" 6587 msgstr "" 6588 6589 #: src/components/Post/Embed/ExternalEmbed/index.tsx:79 6590 msgid "Open link to {niceUrl}" 6591 msgstr "" ··· 6703 msgid "Opens image picker" 6704 msgstr "" 6705 6706 - #: src/components/dialogs/LinkWarning.tsx:97 6707 msgid "Opens link {0}" 6708 msgstr "" 6709 ··· 6846 msgid "Password updated!" 6847 msgstr "" 6848 6849 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:151 6850 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:385 6851 msgid "Pause" 6852 msgstr "" ··· 6859 msgid "Pause video" 6860 msgstr "" 6861 6862 - #: src/screens/ProfileList/index.tsx:166 6863 #: src/screens/Search/SearchResults.tsx:72 6864 #: src/screens/StarterPack/StarterPackScreen.tsx:192 6865 msgid "People" ··· 6960 msgid "Pinned to your feeds" 6961 msgstr "" 6962 6963 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:151 6964 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:386 6965 msgid "Play" 6966 msgstr "" ··· 6986 msgid "Plays or pauses the GIF" 6987 msgstr "" 6988 6989 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152 6990 msgid "Plays or pauses the video" 6991 msgstr "" 6992 ··· 7256 7257 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:250 7258 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:262 7259 - #: src/screens/ProfileList/index.tsx:166 7260 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:216 7261 #: src/screens/StarterPack/StarterPackScreen.tsx:194 7262 #: src/view/screens/Profile.tsx:234 ··· 7275 msgid "Posts, Replies" 7276 msgstr "" 7277 7278 - #: src/components/dialogs/LinkWarning.tsx:73 7279 msgid "Potentially misleading link" 7280 msgstr "" 7281 7282 - #: src/components/dialogs/LinkWarning.tsx:66 7283 msgid "Potentially misleading link warning" 7284 msgstr "" 7285 ··· 7378 #: src/view/shell/Drawer.tsx:79 7379 #: src/view/shell/Drawer.tsx:598 7380 msgid "Profile" 7381 msgstr "" 7382 7383 #: src/screens/Profile/Header/EditProfileDialog.tsx:189 ··· 7601 7602 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:171 7603 #: src/components/dialogs/MutedWords.tsx:443 7604 - #: src/components/dialogs/StarterPackDialog.tsx:375 7605 - #: src/components/dialogs/StarterPackDialog.tsx:381 7606 #: src/components/FeedCard.tsx:375 7607 #: src/components/StarterPack/Wizard/WizardListCard.tsx:105 7608 #: src/components/StarterPack/Wizard/WizardListCard.tsx:112 ··· 7726 7727 #: src/components/verification/VerificationRemovePrompt.tsx:46 7728 #: src/components/verification/VerificationsDialog.tsx:249 7729 - #: src/view/com/profile/ProfileMenu.tsx:409 7730 - #: src/view/com/profile/ProfileMenu.tsx:412 7731 msgid "Remove verification" 7732 msgstr "" 7733 ··· 7757 msgid "Removed from saved posts" 7758 msgstr "" 7759 7760 - #: src/components/dialogs/StarterPackDialog.tsx:278 7761 msgid "Removed from starter pack" 7762 msgstr "" 7763 ··· 7862 msgid "Report" 7863 msgstr "" 7864 7865 - #: src/view/com/profile/ProfileMenu.tsx:476 7866 - #: src/view/com/profile/ProfileMenu.tsx:479 7867 msgid "Report account" 7868 msgstr "" 7869 ··· 8758 msgid "Share a fun fact!" 8759 msgstr "" 8760 8761 - #: src/view/com/profile/ProfileMenu.tsx:563 8762 msgid "Share anyway" 8763 msgstr "" 8764 ··· 8767 msgid "Share author DID" 8768 msgstr "" 8769 8770 - #: src/components/dialogs/LinkWarning.tsx:96 8771 - #: src/components/dialogs/LinkWarning.tsx:104 8772 #: src/components/StarterPack/ShareDialog.tsx:114 8773 #: src/components/StarterPack/ShareDialog.tsx:123 8774 msgid "Share link" ··· 9437 msgid "Task complete - 10 likes!" 9438 msgstr "" 9439 9440 - #: src/components/ProgressGuide/List.tsx:97 9441 msgid "Teach our algorithm what you like" 9442 msgstr "" 9443 ··· 9522 msgid "That's everything!" 9523 msgstr "" 9524 9525 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:186 9526 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:394 9527 - #: src/view/com/profile/ProfileMenu.tsx:539 9528 msgid "The account will be able to interact with you after unblocking." 9529 msgstr "" 9530 ··· 9707 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:450 9708 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:116 9709 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:127 9710 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:88 9711 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:253 9712 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:279 9713 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:294 9714 #: src/view/com/profile/ProfileMenu.tsx:152 9715 #: src/view/com/profile/ProfileMenu.tsx:162 9716 #: src/view/com/profile/ProfileMenu.tsx:176 ··· 9787 #: src/screens/PostThread/components/ThreadItemAnchorNoUnauthenticated.tsx:27 9788 #: src/screens/PostThread/components/ThreadItemPostNoUnauthenticated.tsx:47 9789 msgid "This author has chosen to make their posts visible only to people who are signed in." 9790 msgstr "" 9791 9792 #: src/screens/Messages/components/MessageListError.tsx:18 ··· 9897 msgid "This labeler hasn't declared what labels it publishes, and may not be active." 9898 msgstr "" 9899 9900 - #: src/components/dialogs/LinkWarning.tsx:79 9901 msgid "This link is taking you to the following website:" 9902 msgstr "" 9903 ··· 9941 msgid "This post's author has disabled quote posts." 9942 msgstr "" 9943 9944 - #: src/view/com/profile/ProfileMenu.tsx:560 9945 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in." 9946 msgstr "" 9947 ··· 10070 msgid "Toggle to enable or disable adult content" 10071 msgstr "" 10072 10073 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:169 10074 msgid "Toggles the sound" 10075 msgstr "" 10076 ··· 10189 msgid "Unable to delete" 10190 msgstr "" 10191 10192 #: src/screens/Settings/Settings.tsx:523 10193 msgid "Unapply Pull Request" 10194 msgstr "" ··· 10209 #: src/components/dms/MessagesListBlockedFooter.tsx:104 10210 #: src/components/dms/MessagesListBlockedFooter.tsx:112 10211 #: src/components/dms/MessagesListBlockedFooter.tsx:119 10212 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 10213 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:337 10214 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:397 10215 #: src/screens/ProfileList/components/Header.tsx:165 10216 #: src/screens/ProfileList/components/Header.tsx:172 10217 - #: src/view/com/profile/ProfileMenu.tsx:551 10218 msgid "Unblock" 10219 msgstr "" 10220 10221 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:341 10222 msgctxt "action" 10223 msgid "Unblock" 10224 msgstr "" 10225 10226 #: src/components/dms/ConvoMenu.tsx:261 10227 #: src/components/dms/ConvoMenu.tsx:264 10228 - #: src/view/com/profile/ProfileMenu.tsx:456 10229 - #: src/view/com/profile/ProfileMenu.tsx:462 10230 msgid "Unblock account" 10231 msgstr "" 10232 10233 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:184 10234 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:392 10235 - #: src/view/com/profile/ProfileMenu.tsx:533 10236 msgid "Unblock Account?" 10237 msgstr "" 10238 ··· 10252 #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:78 10253 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:39 10254 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:45 10255 msgid "Undo" 10256 msgstr "" 10257 ··· 10265 msgid "Undo repost ({0, plural, one {# repost} other {# reposts}})" 10266 msgstr "" 10267 10268 - #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:368 10269 msgid "Unfollow {0}" 10270 msgstr "" 10271 ··· 10311 msgid "Unlike ({0, plural, one {# like} other {# likes}})" 10312 msgstr "" 10313 10314 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:166 10315 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96 10316 msgctxt "video" 10317 msgid "Unmute" ··· 10329 10330 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:688 10331 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:694 10332 - #: src/view/com/profile/ProfileMenu.tsx:435 10333 - #: src/view/com/profile/ProfileMenu.tsx:441 10334 msgid "Unmute account" 10335 msgstr "" 10336 ··· 10646 10647 #: src/components/verification/VerificationCreatePrompt.tsx:84 10648 #: src/components/verification/VerificationCreatePrompt.tsx:86 10649 - #: src/view/com/profile/ProfileMenu.tsx:419 10650 - #: src/view/com/profile/ProfileMenu.tsx:422 10651 msgid "Verify account" 10652 msgstr "" 10653 ··· 10728 msgid "Version {0}" 10729 msgstr "" 10730 10731 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:84 10732 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:145 10733 msgid "Video" 10734 msgstr "" 10735 ··· 10774 msgid "Video uploaded" 10775 msgstr "" 10776 10777 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:84 10778 msgid "Video: {0}" 10779 msgstr "" 10780 ··· 10918 msgid "Violent or threatening content" 10919 msgstr "" 10920 10921 - #: src/components/dialogs/LinkWarning.tsx:96 10922 - #: src/components/dialogs/LinkWarning.tsx:106 10923 msgid "Visit site" 10924 msgstr "" 10925 ··· 11090 msgid "We're sorry, but based on your device's location, you are currently located in a region that requires age assurance." 11091 msgstr "" 11092 11093 - #: src/screens/ProfileList/index.tsx:87 11094 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." 11095 msgstr "" 11096 ··· 11494 msgid "You have no lists." 11495 msgstr "" 11496 11497 - #: src/components/dialogs/StarterPackDialog.tsx:101 11498 msgid "You have no starter packs." 11499 msgstr "" 11500
··· 132 msgid "{0, plural, other {# people have}} joined Bluesky via this starter pack!" 133 msgstr "" 134 135 + #: src/components/dialogs/StarterPackDialog.tsx:358 136 msgid "{0, plural, other {+# more}}" 137 msgstr "" 138 ··· 643 msgid "Account followed" 644 msgstr "" 645 646 + #: src/lib/strings/errors.ts:34 647 + msgid "Account has been suspended" 648 + msgstr "" 649 + 650 + #: src/lib/strings/errors.ts:37 651 + msgid "Account is deactivated" 652 + msgstr "" 653 + 654 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:446 655 #: src/view/com/profile/ProfileMenu.tsx:158 656 msgctxt "toast" ··· 678 msgid "Account removed from quick access" 679 msgstr "" 680 681 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:85 682 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:295 683 #: src/view/com/profile/ProfileMenu.tsx:172 684 msgctxt "toast" 685 msgid "Account unblocked" ··· 712 713 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:169 714 #: src/components/dialogs/MutedWords.tsx:333 715 + #: src/components/dialogs/StarterPackDialog.tsx:372 716 + #: src/components/dialogs/StarterPackDialog.tsx:379 717 #: src/view/com/modals/UserAddRemoveLists.tsx:235 718 msgid "Add" 719 msgstr "" ··· 843 msgid "Add this feed to your feeds" 844 msgstr "" 845 846 + #: src/view/com/profile/ProfileMenu.tsx:345 847 + #: src/view/com/profile/ProfileMenu.tsx:348 848 msgid "Add to lists" 849 msgstr "" 850 ··· 852 msgid "Add to saved posts" 853 msgstr "" 854 855 + #: src/components/dialogs/StarterPackDialog.tsx:182 856 + #: src/view/com/profile/ProfileMenu.tsx:335 857 + #: src/view/com/profile/ProfileMenu.tsx:338 858 msgid "Add to starter packs" 859 msgstr "" 860 ··· 871 msgid "Added to list" 872 msgstr "" 873 874 + #: src/components/dialogs/StarterPackDialog.tsx:270 875 msgid "Added to starter pack" 876 msgstr "" 877 ··· 1443 msgid "Before creating a post or replying, you must first verify your email." 1444 msgstr "" 1445 1446 + #: src/components/dialogs/StarterPackDialog.tsx:70 1447 #: src/components/StarterPack/ProfileStarterPacks.tsx:263 1448 #: src/components/StarterPack/ProfileStarterPacks.tsx:273 1449 #: src/view/screens/Profile.tsx:351 ··· 1490 msgstr "" 1491 1492 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:821 1493 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:195 1494 + #: src/view/com/profile/ProfileMenu.tsx:553 1495 msgid "Block" 1496 msgstr "" 1497 ··· 1501 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:708 1502 #: src/screens/Messages/components/RequestButtons.tsx:144 1503 #: src/screens/Messages/components/RequestButtons.tsx:146 1504 + #: src/view/com/profile/ProfileMenu.tsx:459 1505 + #: src/view/com/profile/ProfileMenu.tsx:466 1506 msgid "Block account" 1507 msgstr "" 1508 1509 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:816 1510 + #: src/view/com/profile/ProfileMenu.tsx:536 1511 msgid "Block Account?" 1512 msgstr "" 1513 ··· 1559 msgstr "" 1560 1561 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:818 1562 + #: src/view/com/profile/ProfileMenu.tsx:548 1563 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1564 msgstr "" 1565 ··· 1575 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1576 msgstr "" 1577 1578 + #: src/view/com/profile/ProfileMenu.tsx:545 1579 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." 1580 msgstr "" 1581 ··· 1609 msgid "Bluesky is an open network where you can choose your own provider. If you're new here, we recommend sticking with the default Bluesky Social option." 1610 msgstr "" 1611 1612 + #: src/components/ProgressGuide/List.tsx:108 1613 msgid "Bluesky is better with friends!" 1614 msgstr "" 1615 ··· 2112 #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:190 2113 #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:198 2114 #: src/components/dialogs/SearchablePeopleList.tsx:295 2115 + #: src/components/dialogs/StarterPackDialog.tsx:185 2116 #: src/components/dms/AfterReportDialog.tsx:93 2117 #: src/components/dms/AfterReportDialog.tsx:98 2118 #: src/components/dms/AfterReportDialog.tsx:208 ··· 2485 msgid "Copy App Password" 2486 msgstr "" 2487 2488 + #: src/view/com/profile/ProfileMenu.tsx:496 2489 + #: src/view/com/profile/ProfileMenu.tsx:499 2490 msgid "Copy at:// URI" 2491 msgstr "" 2492 ··· 2501 msgstr "" 2502 2503 #: src/screens/Settings/components/ChangeHandleDialog.tsx:502 2504 + #: src/view/com/profile/ProfileMenu.tsx:505 2505 + #: src/view/com/profile/ProfileMenu.tsx:508 2506 msgid "Copy DID" 2507 msgstr "" 2508 ··· 2602 msgstr "" 2603 2604 #: src/screens/ProfileList/components/ErrorScreen.tsx:26 2605 + #: src/screens/ProfileList/index.tsx:80 2606 + #: src/screens/ProfileList/index.tsx:102 2607 msgid "Could not load list" 2608 msgstr "" 2609 ··· 2637 2638 #. Text on button to create a new starter pack 2639 #. Text on button to create a new starter pack 2640 + #: src/components/dialogs/StarterPackDialog.tsx:111 2641 + #: src/components/dialogs/StarterPackDialog.tsx:208 2642 #: src/components/StarterPack/ProfileStarterPacks.tsx:328 2643 msgid "Create" 2644 msgstr "" ··· 2719 msgid "Create report for {0}" 2720 msgstr "" 2721 2722 + #: src/components/dialogs/StarterPackDialog.tsx:106 2723 + #: src/components/dialogs/StarterPackDialog.tsx:203 2724 msgid "Create starter pack" 2725 msgstr "" 2726 ··· 3033 msgid "Discard post?" 3034 msgstr "" 3035 3036 + #: src/screens/Profile/components/GermButton.tsx:260 3037 + #: src/screens/Profile/components/GermButton.tsx:267 3038 + msgid "Disconnect Germ DM" 3039 + msgstr "" 3040 + 3041 #: src/screens/Settings/components/PwiOptOut.tsx:87 3042 #: src/screens/Settings/components/PwiOptOut.tsx:91 3043 msgid "Discourage apps from showing my account to logged-out users" ··· 3065 msgid "Dismiss error" 3066 msgstr "" 3067 3068 + #: src/components/ProgressGuide/List.tsx:73 3069 msgid "Dismiss getting started guide" 3070 msgstr "" 3071 ··· 3292 msgid "Edit list details" 3293 msgstr "" 3294 3295 + #: src/view/com/profile/ProfileMenu.tsx:359 3296 + #: src/view/com/profile/ProfileMenu.tsx:379 3297 msgid "Edit live status" 3298 msgstr "" 3299 ··· 3322 #: src/screens/Profile/Header/EditProfileDialog.tsx:268 3323 #: src/screens/Profile/Header/EditProfileDialog.tsx:274 3324 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:295 3325 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:329 3326 msgid "Edit profile" 3327 msgstr "" 3328 3329 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:298 3330 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:331 3331 msgid "Edit Profile" 3332 msgstr "" 3333 ··· 3537 msgid "Enter your username and password" 3538 msgstr "" 3539 3540 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:148 3541 msgid "Enters full screen" 3542 msgstr "" 3543 ··· 3723 msgid "Failed to add emoji reaction" 3724 msgstr "" 3725 3726 + #: src/components/dialogs/StarterPackDialog.tsx:276 3727 msgid "Failed to add to starter pack" 3728 msgstr "" 3729 ··· 3760 3761 #: src/screens/StarterPack/StarterPackScreen.tsx:732 3762 msgid "Failed to delete starter pack" 3763 + msgstr "" 3764 + 3765 + #: src/screens/Profile/components/GermButton.tsx:194 3766 + msgid "Failed to disconnect Germ DM. Error: {0}" 3767 msgstr "" 3768 3769 #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:133 ··· 3840 msgid "Failed to pin post" 3841 msgstr "" 3842 3843 + #: src/screens/Profile/components/GermButton.tsx:162 3844 + msgid "Failed to reconnect Germ DM. Error: {0}" 3845 + msgstr "" 3846 + 3847 #: src/screens/Settings/FindContactsSettings.tsx:487 3848 msgid "Failed to remove data due to a network error, please check your internet connection." 3849 msgstr "" ··· 3857 msgid "Failed to remove emoji reaction" 3858 msgstr "" 3859 3860 + #: src/components/dialogs/StarterPackDialog.tsx:289 3861 msgid "Failed to remove from starter pack" 3862 msgstr "" 3863 ··· 4147 #: src/components/ProfileHoverCard/index.web.tsx:497 4148 #: src/components/ProfileHoverCard/index.web.tsx:508 4149 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:152 4150 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:386 4151 #: src/screens/VideoFeed/index.tsx:874 4152 #: src/view/com/notifications/NotificationFeedItem.tsx:841 4153 #: src/view/com/notifications/NotificationFeedItem.tsx:848 ··· 4155 msgstr "" 4156 4157 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:139 4158 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:374 4159 msgid "Follow {0}" 4160 msgstr "" 4161 ··· 4167 msgid "Follow 10 accounts" 4168 msgstr "" 4169 4170 + #: src/components/ProgressGuide/List.tsx:66 4171 msgid "Follow 10 people to get started" 4172 msgstr "" 4173 4174 + #: src/components/ProgressGuide/List.tsx:107 4175 msgid "Follow 7 accounts" 4176 msgstr "" 4177 ··· 4199 #. User is not following this account, click to follow back 4200 #: src/components/ProfileCard.tsx:553 4201 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:150 4202 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:384 4203 #: src/view/com/notifications/NotificationFeedItem.tsx:841 4204 #: src/view/com/notifications/NotificationFeedItem.tsx:848 4205 msgid "Follow back" ··· 4244 #: src/components/ProfileHoverCard/index.web.tsx:496 4245 #: src/components/ProfileHoverCard/index.web.tsx:507 4246 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:155 4247 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:382 4248 #: src/screens/VideoFeed/index.tsx:872 4249 #: src/view/com/notifications/NotificationFeedItem.tsx:819 4250 #: src/view/com/notifications/NotificationFeedItem.tsx:836 ··· 4258 msgstr "" 4259 4260 #: src/components/ProfileCard.tsx:509 4261 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:249 4262 #: src/view/com/notifications/NotificationFeedItem.tsx:772 4263 msgid "Following {0}" 4264 msgstr "" ··· 4360 msgid "Generate a starter pack" 4361 msgstr "" 4362 4363 + #: src/screens/Profile/components/GermButton.tsx:85 4364 + #: src/screens/Profile/components/GermButton.tsx:223 4365 + msgid "Germ DM" 4366 + msgstr "" 4367 + 4368 + #: src/screens/Profile/components/GermButton.tsx:181 4369 + msgid "Germ DM disconnected" 4370 + msgstr "" 4371 + 4372 + #: src/screens/Profile/components/GermButton.tsx:232 4373 + #: src/screens/Profile/components/GermButton.tsx:237 4374 + msgid "Germ DM Link" 4375 + msgstr "" 4376 + 4377 + #: src/screens/Profile/components/GermButton.tsx:158 4378 + msgid "Germ DM reconnected" 4379 + msgstr "" 4380 + 4381 #: src/view/shell/Drawer.tsx:374 4382 msgid "Get help" 4383 msgstr "" ··· 4460 msgid "Glorification of violence" 4461 msgstr "" 4462 4463 + #: src/components/dialogs/LinkWarning.tsx:126 4464 + #: src/components/dialogs/LinkWarning.tsx:132 4465 #: src/components/Layout/Header/index.tsx:128 4466 #: src/components/moderation/ScreenHider.tsx:160 4467 #: src/components/moderation/ScreenHider.tsx:169 ··· 4510 msgid "Go Home" 4511 msgstr "" 4512 4513 + #: src/view/com/profile/ProfileMenu.tsx:360 4514 + #: src/view/com/profile/ProfileMenu.tsx:381 4515 msgid "Go live" 4516 msgstr "" 4517 ··· 4522 msgid "Go Live" 4523 msgstr "" 4524 4525 + #: src/view/com/profile/ProfileMenu.tsx:357 4526 + #: src/view/com/profile/ProfileMenu.tsx:377 4527 msgid "Go live (disabled)" 4528 msgstr "" 4529 ··· 4566 4567 #: src/components/live/GoLiveDisabledDialog.tsx:104 4568 msgid "Going live is currently disabled for your account" 4569 + msgstr "" 4570 + 4571 + #: src/screens/Profile/components/GermButton.tsx:251 4572 + #: src/screens/Profile/components/GermButton.tsx:256 4573 + msgid "Got it" 4574 msgstr "" 4575 4576 #: src/lib/moderation/useGlobalLabelStrings.ts:46 ··· 5328 msgid "Learn more about what is public on Bluesky." 5329 msgstr "" 5330 5331 + #: src/screens/Profile/components/GermButton.tsx:210 5332 + msgid "Learn more about your Germ DM link" 5333 + msgstr "" 5334 + 5335 #: src/components/ageAssurance/AgeAssuranceAdmonition.tsx:89 5336 msgid "Learn more in your <0>account settings.</0>" 5337 msgstr "" ··· 5362 msgid "Leave them all unselected to see any language." 5363 msgstr "" 5364 5365 + #: src/components/dialogs/LinkWarning.tsx:82 5366 + #: src/components/dialogs/LinkWarning.tsx:90 5367 msgid "Leaving Bluesky" 5368 msgstr "" 5369 ··· 5404 msgid "Like ({0, plural, one {# like} other {# likes}})" 5405 msgstr "" 5406 5407 + #: src/components/ProgressGuide/List.tsx:101 5408 msgid "Like 10 posts" 5409 msgstr "" 5410 ··· 5521 msgid "List has been hidden" 5522 msgstr "" 5523 5524 + #: src/screens/ProfileList/index.tsx:176 5525 msgid "List Hidden" 5526 msgstr "" 5527 ··· 5691 msgid "Make one for me" 5692 msgstr "" 5693 5694 + #: src/components/dialogs/LinkWarning.tsx:100 5695 msgid "Make sure this is where you intend to go!" 5696 msgstr "" 5697 ··· 5918 msgid "Music" 5919 msgstr "" 5920 5921 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:169 5922 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:97 5923 msgctxt "video" 5924 msgid "Mute" ··· 5931 5932 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:689 5933 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:695 5934 + #: src/view/com/profile/ProfileMenu.tsx:438 5935 + #: src/view/com/profile/ProfileMenu.tsx:445 5936 msgid "Mute account" 5937 msgstr "" 5938 ··· 6062 msgstr "" 6063 6064 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:193 6065 + #: src/view/com/profile/ProfileMenu.tsx:393 6066 msgid "New" 6067 msgstr "" 6068 ··· 6129 msgstr "" 6130 6131 #: src/screens/Profile/ProfileFeed/index.tsx:250 6132 + #: src/screens/ProfileList/index.tsx:250 6133 + #: src/screens/ProfileList/index.tsx:299 6134 #: src/view/screens/Feeds.tsx:552 6135 #: src/view/screens/Notifications.tsx:166 6136 #: src/view/screens/Profile.tsx:594 ··· 6155 msgid "New posts from {firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}}" 6156 msgstr "" 6157 6158 + #: src/components/dialogs/StarterPackDialog.tsx:200 6159 msgid "New starter pack" 6160 msgstr "" 6161 ··· 6259 msgstr "" 6260 6261 #: src/components/ProfileCard.tsx:531 6262 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:274 6263 #: src/view/com/notifications/NotificationFeedItem.tsx:792 6264 msgid "No longer following {0}" 6265 msgstr "" ··· 6421 msgid "Not ready to hit post? Keep your best ideas in Drafts until the timing is just right." 6422 msgstr "" 6423 6424 + #: src/view/com/profile/ProfileMenu.tsx:560 6425 msgid "Note about sharing" 6426 msgstr "" 6427 ··· 6634 msgid "Open full emoji list" 6635 msgstr "" 6636 6637 + #: src/screens/Profile/components/GermButton.tsx:74 6638 + msgid "Open Germ DM" 6639 + msgstr "" 6640 + 6641 #: src/components/Post/Embed/ExternalEmbed/index.tsx:79 6642 msgid "Open link to {niceUrl}" 6643 msgstr "" ··· 6755 msgid "Opens image picker" 6756 msgstr "" 6757 6758 + #: src/components/dialogs/LinkWarning.tsx:112 6759 msgid "Opens link {0}" 6760 msgstr "" 6761 ··· 6898 msgid "Password updated!" 6899 msgstr "" 6900 6901 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 6902 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:385 6903 msgid "Pause" 6904 msgstr "" ··· 6911 msgid "Pause video" 6912 msgstr "" 6913 6914 + #: src/screens/ProfileList/index.tsx:168 6915 #: src/screens/Search/SearchResults.tsx:72 6916 #: src/screens/StarterPack/StarterPackScreen.tsx:192 6917 msgid "People" ··· 7012 msgid "Pinned to your feeds" 7013 msgstr "" 7014 7015 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 7016 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:386 7017 msgid "Play" 7018 msgstr "" ··· 7038 msgid "Plays or pauses the GIF" 7039 msgstr "" 7040 7041 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:154 7042 msgid "Plays or pauses the video" 7043 msgstr "" 7044 ··· 7308 7309 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:250 7310 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:262 7311 + #: src/screens/ProfileList/index.tsx:168 7312 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:216 7313 #: src/screens/StarterPack/StarterPackScreen.tsx:194 7314 #: src/view/screens/Profile.tsx:234 ··· 7327 msgid "Posts, Replies" 7328 msgstr "" 7329 7330 + #: src/components/dialogs/LinkWarning.tsx:88 7331 msgid "Potentially misleading link" 7332 msgstr "" 7333 7334 + #: src/components/dialogs/LinkWarning.tsx:81 7335 msgid "Potentially misleading link warning" 7336 msgstr "" 7337 ··· 7430 #: src/view/shell/Drawer.tsx:79 7431 #: src/view/shell/Drawer.tsx:598 7432 msgid "Profile" 7433 + msgstr "" 7434 + 7435 + #: src/lib/strings/errors.ts:40 7436 + msgid "Profile not found" 7437 msgstr "" 7438 7439 #: src/screens/Profile/Header/EditProfileDialog.tsx:189 ··· 7657 7658 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:171 7659 #: src/components/dialogs/MutedWords.tsx:443 7660 + #: src/components/dialogs/StarterPackDialog.tsx:372 7661 + #: src/components/dialogs/StarterPackDialog.tsx:379 7662 #: src/components/FeedCard.tsx:375 7663 #: src/components/StarterPack/Wizard/WizardListCard.tsx:105 7664 #: src/components/StarterPack/Wizard/WizardListCard.tsx:112 ··· 7782 7783 #: src/components/verification/VerificationRemovePrompt.tsx:46 7784 #: src/components/verification/VerificationsDialog.tsx:249 7785 + #: src/view/com/profile/ProfileMenu.tsx:411 7786 + #: src/view/com/profile/ProfileMenu.tsx:414 7787 msgid "Remove verification" 7788 msgstr "" 7789 ··· 7813 msgid "Removed from saved posts" 7814 msgstr "" 7815 7816 + #: src/components/dialogs/StarterPackDialog.tsx:283 7817 msgid "Removed from starter pack" 7818 msgstr "" 7819 ··· 7918 msgid "Report" 7919 msgstr "" 7920 7921 + #: src/view/com/profile/ProfileMenu.tsx:478 7922 + #: src/view/com/profile/ProfileMenu.tsx:481 7923 msgid "Report account" 7924 msgstr "" 7925 ··· 8814 msgid "Share a fun fact!" 8815 msgstr "" 8816 8817 + #: src/view/com/profile/ProfileMenu.tsx:565 8818 msgid "Share anyway" 8819 msgstr "" 8820 ··· 8823 msgid "Share author DID" 8824 msgstr "" 8825 8826 + #: src/components/dialogs/LinkWarning.tsx:111 8827 + #: src/components/dialogs/LinkWarning.tsx:119 8828 #: src/components/StarterPack/ShareDialog.tsx:114 8829 #: src/components/StarterPack/ShareDialog.tsx:123 8830 msgid "Share link" ··· 9493 msgid "Task complete - 10 likes!" 9494 msgstr "" 9495 9496 + #: src/components/ProgressGuide/List.tsx:102 9497 msgid "Teach our algorithm what you like" 9498 msgstr "" 9499 ··· 9578 msgid "That's everything!" 9579 msgstr "" 9580 9581 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:191 9582 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:399 9583 + #: src/view/com/profile/ProfileMenu.tsx:541 9584 msgid "The account will be able to interact with you after unblocking." 9585 msgstr "" 9586 ··· 9763 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:450 9764 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:116 9765 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:127 9766 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:89 9767 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:258 9768 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:284 9769 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:299 9770 #: src/view/com/profile/ProfileMenu.tsx:152 9771 #: src/view/com/profile/ProfileMenu.tsx:162 9772 #: src/view/com/profile/ProfileMenu.tsx:176 ··· 9843 #: src/screens/PostThread/components/ThreadItemAnchorNoUnauthenticated.tsx:27 9844 #: src/screens/PostThread/components/ThreadItemPostNoUnauthenticated.tsx:47 9845 msgid "This author has chosen to make their posts visible only to people who are signed in." 9846 + msgstr "" 9847 + 9848 + #: src/screens/Profile/components/GermButton.tsx:242 9849 + msgid "This button lets others open the Germ DM app to send you a message. You can manage its visibility from the Germ DM app, or you can disconnect your Bluesky account from Germ DM altogether by clicking the button below." 9850 msgstr "" 9851 9852 #: src/screens/Messages/components/MessageListError.tsx:18 ··· 9957 msgid "This labeler hasn't declared what labels it publishes, and may not be active." 9958 msgstr "" 9959 9960 + #: src/components/dialogs/LinkWarning.tsx:94 9961 msgid "This link is taking you to the following website:" 9962 msgstr "" 9963 ··· 10001 msgid "This post's author has disabled quote posts." 10002 msgstr "" 10003 10004 + #: src/view/com/profile/ProfileMenu.tsx:562 10005 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in." 10006 msgstr "" 10007 ··· 10130 msgid "Toggle to enable or disable adult content" 10131 msgstr "" 10132 10133 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:171 10134 msgid "Toggles the sound" 10135 msgstr "" 10136 ··· 10249 msgid "Unable to delete" 10250 msgstr "" 10251 10252 + #: src/lib/strings/errors.ts:43 10253 + msgid "Unable to resolve handle" 10254 + msgstr "" 10255 + 10256 #: src/screens/Settings/Settings.tsx:523 10257 msgid "Unapply Pull Request" 10258 msgstr "" ··· 10273 #: src/components/dms/MessagesListBlockedFooter.tsx:104 10274 #: src/components/dms/MessagesListBlockedFooter.tsx:112 10275 #: src/components/dms/MessagesListBlockedFooter.tsx:119 10276 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:195 10277 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:342 10278 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:402 10279 #: src/screens/ProfileList/components/Header.tsx:165 10280 #: src/screens/ProfileList/components/Header.tsx:172 10281 + #: src/view/com/profile/ProfileMenu.tsx:553 10282 msgid "Unblock" 10283 msgstr "" 10284 10285 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:346 10286 msgctxt "action" 10287 msgid "Unblock" 10288 msgstr "" 10289 10290 #: src/components/dms/ConvoMenu.tsx:261 10291 #: src/components/dms/ConvoMenu.tsx:264 10292 + #: src/view/com/profile/ProfileMenu.tsx:458 10293 + #: src/view/com/profile/ProfileMenu.tsx:464 10294 msgid "Unblock account" 10295 msgstr "" 10296 10297 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:189 10298 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:397 10299 + #: src/view/com/profile/ProfileMenu.tsx:535 10300 msgid "Unblock Account?" 10301 msgstr "" 10302 ··· 10316 #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:78 10317 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:39 10318 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:45 10319 + #: src/screens/Profile/components/GermButton.tsx:184 10320 + #: src/screens/Profile/components/GermButton.tsx:185 10321 msgid "Undo" 10322 msgstr "" 10323 ··· 10331 msgid "Undo repost ({0, plural, one {# repost} other {# reposts}})" 10332 msgstr "" 10333 10334 + #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:373 10335 msgid "Unfollow {0}" 10336 msgstr "" 10337 ··· 10377 msgid "Unlike ({0, plural, one {# like} other {# likes}})" 10378 msgstr "" 10379 10380 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:168 10381 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96 10382 msgctxt "video" 10383 msgid "Unmute" ··· 10395 10396 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:688 10397 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:694 10398 + #: src/view/com/profile/ProfileMenu.tsx:437 10399 + #: src/view/com/profile/ProfileMenu.tsx:443 10400 msgid "Unmute account" 10401 msgstr "" 10402 ··· 10712 10713 #: src/components/verification/VerificationCreatePrompt.tsx:84 10714 #: src/components/verification/VerificationCreatePrompt.tsx:86 10715 + #: src/view/com/profile/ProfileMenu.tsx:421 10716 + #: src/view/com/profile/ProfileMenu.tsx:424 10717 msgid "Verify account" 10718 msgstr "" 10719 ··· 10794 msgid "Version {0}" 10795 msgstr "" 10796 10797 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:86 10798 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:147 10799 msgid "Video" 10800 msgstr "" 10801 ··· 10840 msgid "Video uploaded" 10841 msgstr "" 10842 10843 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:86 10844 msgid "Video: {0}" 10845 msgstr "" 10846 ··· 10984 msgid "Violent or threatening content" 10985 msgstr "" 10986 10987 + #: src/components/dialogs/LinkWarning.tsx:111 10988 + #: src/components/dialogs/LinkWarning.tsx:121 10989 msgid "Visit site" 10990 msgstr "" 10991 ··· 11156 msgid "We're sorry, but based on your device's location, you are currently located in a region that requires age assurance." 11157 msgstr "" 11158 11159 + #: src/screens/ProfileList/index.tsx:88 11160 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." 11161 msgstr "" 11162 ··· 11560 msgid "You have no lists." 11561 msgstr "" 11562 11563 + #: src/components/dialogs/StarterPackDialog.tsx:100 11564 msgid "You have no starter packs." 11565 msgstr "" 11566
+5
src/screens/Profile/Header/ProfileHeaderStandard.tsx
··· 48 import {Text} from '#/components/Typography' 49 import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' 50 import {IS_IOS} from '#/env' 51 import {EditProfileDialog} from './EditProfileDialog' 52 import {ProfileHeaderHandle} from './Handle' 53 import {ProfileHeaderMetrics} from './Metrics' ··· 182 /> 183 </View> 184 ) : undefined} 185 186 {!isMe && 187 !disableFollowedByMetrics &&
··· 48 import {Text} from '#/components/Typography' 49 import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' 50 import {IS_IOS} from '#/env' 51 + import {GermButton} from '../components/GermButton' 52 import {EditProfileDialog} from './EditProfileDialog' 53 import {ProfileHeaderHandle} from './Handle' 54 import {ProfileHeaderMetrics} from './Metrics' ··· 183 /> 184 </View> 185 ) : undefined} 186 + 187 + {profile.associated?.germ && ( 188 + <GermButton germ={profile.associated.germ} profile={profile} /> 189 + )} 190 191 {!isMe && 192 !disableFollowedByMetrics &&
+334
src/screens/Profile/components/GermButton.tsx
···
··· 1 + import {Platform, View} from 'react-native' 2 + import {Image} from 'expo-image' 3 + import { 4 + type AppBskyActorDefs, 5 + type AppBskyActorGetProfile, 6 + type AtpAgent, 7 + } from '@atproto/api' 8 + import {msg, Trans} from '@lingui/macro' 9 + import {useLingui} from '@lingui/react' 10 + import {useMutation, useQueryClient} from '@tanstack/react-query' 11 + 12 + import {until} from '#/lib/async/until' 13 + import {isNetworkError} from '#/lib/strings/errors' 14 + import {RQKEY} from '#/state/queries/profile' 15 + import {useAgent, useSession} from '#/state/session' 16 + import {atoms as a, useTheme, web} from '#/alf' 17 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 18 + import * as Dialog from '#/components/Dialog' 19 + import {CustomLinkWarningDialog} from '#/components/dialogs/LinkWarning' 20 + import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRightIcon} from '#/components/icons/Arrow' 21 + import {Link} from '#/components/Link' 22 + import {Loader} from '#/components/Loader' 23 + import * as Toast from '#/components/Toast' 24 + import {Text} from '#/components/Typography' 25 + import {useAnalytics} from '#/analytics' 26 + import type * as bsky from '#/types/bsky' 27 + 28 + export function GermButton({ 29 + germ, 30 + profile, 31 + }: { 32 + germ: AppBskyActorDefs.ProfileAssociatedGerm 33 + profile: bsky.profile.AnyProfileView 34 + }) { 35 + const t = useTheme() 36 + const ax = useAnalytics() 37 + const {_} = useLingui() 38 + const {currentAccount} = useSession() 39 + const linkWarningControl = Dialog.useDialogControl() 40 + 41 + // exclude `none` and all unknown values 42 + if ( 43 + !(germ.showButtonTo === 'everyone' || germ.showButtonTo === 'usersIFollow') 44 + ) { 45 + return null 46 + } 47 + 48 + if (currentAccount?.did === profile.did) { 49 + return <GermSelfButton did={currentAccount.did} /> 50 + } 51 + 52 + if (germ.showButtonTo === 'usersIFollow' && !profile.viewer?.followedBy) { 53 + return null 54 + } 55 + 56 + const url = constructGermUrl(germ, profile, currentAccount?.did) 57 + 58 + if (!url) { 59 + return null 60 + } 61 + 62 + return ( 63 + <> 64 + <Link 65 + to={url} 66 + onPress={evt => { 67 + ax.metric('profile:associated:germ:click-to-chat', {}) 68 + if (isCustomGermDomain(url)) { 69 + evt.preventDefault() 70 + linkWarningControl.open() 71 + return false 72 + } 73 + }} 74 + label={_(msg`Open Germ DM`)} 75 + overridePresentation={false} 76 + shouldProxy={false} 77 + style={[ 78 + t.atoms.bg_contrast_50, 79 + a.rounded_full, 80 + a.self_start, 81 + {padding: 6}, 82 + ]}> 83 + <GermLogo size="small" /> 84 + <Text style={[a.text_sm, a.font_medium, a.ml_xs]}> 85 + <Trans>Germ DM</Trans> 86 + </Text> 87 + <ArrowTopRightIcon style={[t.atoms.text, a.mx_2xs]} width={14} /> 88 + </Link> 89 + <CustomLinkWarningDialog 90 + control={linkWarningControl} 91 + link={{ 92 + href: url, 93 + displayText: '', 94 + share: false, 95 + }} 96 + /> 97 + </> 98 + ) 99 + } 100 + 101 + function GermLogo({size}: {size: 'small' | 'large'}) { 102 + return ( 103 + <Image 104 + source={require('../../../../assets/images/germ_logo.webp')} 105 + accessibilityIgnoresInvertColors={false} 106 + contentFit="cover" 107 + style={[ 108 + a.rounded_full, 109 + size === 'large' ? {width: 32, height: 32} : {width: 16, height: 16}, 110 + ]} 111 + /> 112 + ) 113 + } 114 + 115 + function GermSelfButton({did}: {did: string}) { 116 + const t = useTheme() 117 + const ax = useAnalytics() 118 + const {_} = useLingui() 119 + const selfExplanationDialogControl = Dialog.useDialogControl() 120 + const agent = useAgent() 121 + const queryClient = useQueryClient() 122 + 123 + const {mutate: deleteDeclaration, isPending} = useMutation({ 124 + mutationFn: async () => { 125 + const previousRecord = await agent.com.germnetwork.declaration 126 + .get({ 127 + repo: did, 128 + rkey: 'self', 129 + }) 130 + .then(res => res.value) 131 + .catch(() => null) 132 + 133 + await agent.com.germnetwork.declaration.delete({ 134 + repo: did, 135 + rkey: 'self', 136 + }) 137 + 138 + await whenAppViewReady(agent, did, res => !res.data.associated?.germ) 139 + 140 + return previousRecord 141 + }, 142 + onSuccess: previousRecord => { 143 + ax.metric('profile:associated:germ:self-disconnect', {}) 144 + 145 + async function undo() { 146 + if (!previousRecord) return 147 + try { 148 + await agent.com.germnetwork.declaration.put( 149 + { 150 + repo: did, 151 + rkey: 'self', 152 + }, 153 + previousRecord, 154 + ) 155 + await whenAppViewReady(agent, did, res => !!res.data.associated?.germ) 156 + await queryClient.refetchQueries({queryKey: RQKEY(did)}) 157 + 158 + Toast.show(_(msg`Germ DM reconnected`)) 159 + ax.metric('profile:associated:germ:self-reconnect', {}) 160 + } catch (e: any) { 161 + Toast.show( 162 + _(msg`Failed to reconnect Germ DM. Error: ${e?.message}`), 163 + { 164 + type: 'error', 165 + }, 166 + ) 167 + if (!isNetworkError(e)) { 168 + ax.logger.error('Failed to reconnect Germ DM link', { 169 + safeMessage: e, 170 + }) 171 + } 172 + } 173 + } 174 + 175 + selfExplanationDialogControl.close(() => { 176 + void queryClient.refetchQueries({queryKey: RQKEY(did)}) 177 + Toast.show( 178 + <Toast.Outer> 179 + <Toast.Icon /> 180 + <Toast.Text> 181 + <Trans>Germ DM disconnected</Trans> 182 + </Toast.Text> 183 + {previousRecord && ( 184 + <Toast.Action label={_(msg`Undo`)} onPress={() => void undo()}> 185 + <Trans>Undo</Trans> 186 + </Toast.Action> 187 + )} 188 + </Toast.Outer>, 189 + ) 190 + }) 191 + }, 192 + onError: error => { 193 + Toast.show( 194 + _(msg`Failed to disconnect Germ DM. Error: ${error?.message}`), 195 + { 196 + type: 'error', 197 + }, 198 + ) 199 + if (!isNetworkError(error)) { 200 + ax.logger.error('Failed to disconnect Germ DM link', { 201 + safeMessage: error, 202 + }) 203 + } 204 + }, 205 + }) 206 + 207 + return ( 208 + <> 209 + <Button 210 + label={_(msg`Learn more about your Germ DM link`)} 211 + onPress={() => { 212 + ax.metric('profile:associated:germ:click-self-info', {}) 213 + selfExplanationDialogControl.open() 214 + }} 215 + style={[ 216 + t.atoms.bg_contrast_50, 217 + a.rounded_full, 218 + a.self_start, 219 + {padding: 6, paddingRight: 10}, 220 + ]}> 221 + <GermLogo size="small" /> 222 + <Text style={[a.text_sm, a.font_medium, a.ml_xs]}> 223 + <Trans>Germ DM</Trans> 224 + </Text> 225 + </Button> 226 + 227 + <Dialog.Outer 228 + control={selfExplanationDialogControl} 229 + nativeOptions={{preventExpansion: true}}> 230 + <Dialog.Handle /> 231 + <Dialog.ScrollableInner 232 + label={_(msg`Germ DM Link`)} 233 + style={web([{maxWidth: 400, borderRadius: 36}])}> 234 + <View style={[a.flex_row, a.align_center, {gap: 6}]}> 235 + <GermLogo size="large" /> 236 + <Text style={[a.text_2xl, a.font_bold]}> 237 + <Trans>Germ DM Link</Trans> 238 + </Text> 239 + </View> 240 + 241 + <Text style={[a.text_md, a.leading_snug, a.mt_sm]}> 242 + <Trans> 243 + This button lets others open the Germ DM app to send you a 244 + message. You can manage its visibility from the Germ DM app, or 245 + you can disconnect your Bluesky account from Germ DM altogether by 246 + clicking the button below. 247 + </Trans> 248 + </Text> 249 + <View style={[a.mt_2xl, a.gap_md]}> 250 + <Button 251 + label={_(msg`Got it`)} 252 + size="large" 253 + color="primary" 254 + onPress={() => selfExplanationDialogControl.close()}> 255 + <ButtonText> 256 + <Trans>Got it</Trans> 257 + </ButtonText> 258 + </Button> 259 + <Button 260 + label={_(msg`Disconnect Germ DM`)} 261 + size="large" 262 + color="secondary" 263 + onPress={() => deleteDeclaration()} 264 + disabled={isPending}> 265 + {isPending && <ButtonIcon icon={Loader} />} 266 + <ButtonText> 267 + <Trans>Disconnect Germ DM</Trans> 268 + </ButtonText> 269 + </Button> 270 + </View> 271 + </Dialog.ScrollableInner> 272 + </Dialog.Outer> 273 + </> 274 + ) 275 + } 276 + 277 + function constructGermUrl( 278 + declaration: AppBskyActorDefs.ProfileAssociatedGerm, 279 + profile: bsky.profile.AnyProfileView, 280 + viewerDid?: string, 281 + ) { 282 + try { 283 + const urlp = new URL(declaration.messageMeUrl) 284 + 285 + if (urlp.pathname.endsWith('/')) { 286 + urlp.pathname = urlp.pathname.slice(0, -1) 287 + } 288 + 289 + urlp.pathname += `/${platform()}` 290 + 291 + if (viewerDid) { 292 + urlp.hash = `#${profile.did}+${viewerDid}` 293 + } else { 294 + urlp.hash = `#${profile.did}` 295 + } 296 + 297 + return urlp.toString() 298 + } catch { 299 + return null 300 + } 301 + } 302 + 303 + function isCustomGermDomain(url: string) { 304 + try { 305 + const urlp = new URL(url) 306 + return urlp.hostname !== 'landing.ger.mx' 307 + } catch { 308 + return false 309 + } 310 + } 311 + 312 + function platform() { 313 + switch (Platform.OS) { 314 + case 'ios': 315 + return 'iOS' 316 + case 'android': 317 + return 'android' 318 + default: 319 + return 'web' 320 + } 321 + } 322 + 323 + async function whenAppViewReady( 324 + agent: AtpAgent, 325 + actor: string, 326 + fn: (res: AppBskyActorGetProfile.Response) => boolean, 327 + ) { 328 + await until( 329 + 5, // 5 tries 330 + 1e3, // 1s delay between tries 331 + fn, 332 + () => agent.app.bsky.actor.getProfile({actor}), 333 + ) 334 + }
+24 -9
src/screens/ProfileList/index.tsx
··· 1 - import {useCallback, useMemo, useRef} from 'react' 2 import {View} from 'react-native' 3 import {useAnimatedRef} from 'react-native-reanimated' 4 import { ··· 35 import {FAB} from '#/view/com/util/fab/FAB' 36 import {type ListRef} from '#/view/com/util/List' 37 import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' 38 - import {atoms as a, platform} from '#/alf' 39 import {useDialogControl} from '#/components/Dialog' 40 import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' 41 import * as Layout from '#/components/Layout' 42 import {Loader} from '#/components/Loader' 43 import * as Hider from '#/components/moderation/Hider' 44 import {AboutSection} from './AboutSection' 45 import {ErrorScreen} from './components/ErrorScreen' 46 import {Header} from './components/Header' ··· 149 moderationOpts: ModerationOpts 150 preferences: UsePreferencesQueryResponse 151 }) { 152 const {_} = useLingui() 153 const queryClient = useQueryClient() 154 const {openComposer} = useOpenComposer() ··· 164 const scrollElRef = useAnimatedRef() 165 const addUserDialogControl = useDialogControl() 166 const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] 167 168 const moderation = useMemo(() => { 169 return moderateUserList(list, moderationOpts) ··· 263 </Hider.Mask> 264 <Hider.Content> 265 <View style={[a.util_screen_outer]}> 266 - <Layout.Center>{renderHeader()}</Layout.Center> 267 - <AboutSection 268 - list={list} 269 - scrollElRef={scrollElRef as ListRef} 270 - onPressAddUser={addUserDialogControl.open} 271 - headerHeight={0} 272 - /> 273 <FAB 274 testID="composeFAB" 275 onPress={() => openComposer({logContext: 'Fab'})}
··· 1 + import {useCallback, useMemo, useRef, useState} from 'react' 2 import {View} from 'react-native' 3 import {useAnimatedRef} from 'react-native-reanimated' 4 import { ··· 35 import {FAB} from '#/view/com/util/fab/FAB' 36 import {type ListRef} from '#/view/com/util/List' 37 import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' 38 + import {atoms as a, native, platform, useTheme} from '#/alf' 39 import {useDialogControl} from '#/components/Dialog' 40 import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' 41 import * as Layout from '#/components/Layout' 42 import {Loader} from '#/components/Loader' 43 import * as Hider from '#/components/moderation/Hider' 44 + import {IS_WEB} from '#/env' 45 import {AboutSection} from './AboutSection' 46 import {ErrorScreen} from './components/ErrorScreen' 47 import {Header} from './components/Header' ··· 150 moderationOpts: ModerationOpts 151 preferences: UsePreferencesQueryResponse 152 }) { 153 + const t = useTheme() 154 const {_} = useLingui() 155 const queryClient = useQueryClient() 156 const {openComposer} = useOpenComposer() ··· 166 const scrollElRef = useAnimatedRef() 167 const addUserDialogControl = useDialogControl() 168 const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] 169 + // modlist only 170 + const [headerHeight, setHeaderHeight] = useState<number | null>(null) 171 172 const moderation = useMemo(() => { 173 return moderateUserList(list, moderationOpts) ··· 267 </Hider.Mask> 268 <Hider.Content> 269 <View style={[a.util_screen_outer]}> 270 + <Layout.Center 271 + onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)} 272 + style={[ 273 + native([a.absolute, a.z_10, t.atoms.bg]), 274 + 275 + a.border_b, 276 + t.atoms.border_contrast_low, 277 + ]}> 278 + {renderHeader()} 279 + </Layout.Center> 280 + {headerHeight !== null && ( 281 + <AboutSection 282 + list={list} 283 + scrollElRef={scrollElRef as ListRef} 284 + onPressAddUser={addUserDialogControl.open} 285 + headerHeight={IS_WEB ? 0 : headerHeight} 286 + /> 287 + )} 288 <FAB 289 testID="composeFAB" 290 onPress={() => openComposer({logContext: 'Fab'})}
+2 -12
src/screens/Search/modules/ExploreTrendingTopics.tsx
··· 1 import {useMemo} from 'react' 2 import {Pressable, View} from 'react-native' 3 import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api' 4 - import {msg, plural, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' ··· 10 import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery' 11 import {useTrendingConfig} from '#/state/service-config' 12 import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 13 - import {formatCount} from '#/view/com/util/numeric/format' 14 import {atoms as a, useGutters, useTheme, type ViewStyleProp, web} from '#/alf' 15 import {AvatarStack} from '#/components/AvatarStack' 16 import {type Props as SVGIconProps} from '#/components/icons/common' ··· 66 onPress?: () => void 67 }) { 68 const t = useTheme() 69 - const {_, i18n} = useLingui() 70 const gutters = useGutters([0, 'base']) 71 72 const category = useCategoryDisplayName(trend?.category || 'other') ··· 75 (1000 * 60 * 60), 76 ) 77 const badgeType = trend.status === 'hot' ? 'hot' : age < 2 ? 'new' : age 78 - const postCount = trend.postCount 79 - ? _( 80 - plural(trend.postCount, { 81 - other: `${formatCount(i18n, trend.postCount)} posts`, 82 - }), 83 - ) 84 - : null 85 86 const actors = useModerateTrendingActors(trend.actors) 87 ··· 133 web(a.leading_snug), 134 ]} 135 numberOfLines={1}> 136 - {postCount} 137 - {postCount && category && <> &middot; </>} 138 {category} 139 </Text> 140 </View>
··· 1 import {useMemo} from 'react' 2 import {Pressable, View} from 'react-native' 3 import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api' 4 + import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 7 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' ··· 10 import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery' 11 import {useTrendingConfig} from '#/state/service-config' 12 import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 13 import {atoms as a, useGutters, useTheme, type ViewStyleProp, web} from '#/alf' 14 import {AvatarStack} from '#/components/AvatarStack' 15 import {type Props as SVGIconProps} from '#/components/icons/common' ··· 65 onPress?: () => void 66 }) { 67 const t = useTheme() 68 + const {_} = useLingui() 69 const gutters = useGutters([0, 'base']) 70 71 const category = useCategoryDisplayName(trend?.category || 'other') ··· 74 (1000 * 60 * 60), 75 ) 76 const badgeType = trend.status === 'hot' ? 'hot' : age < 2 ? 'new' : age 77 78 const actors = useModerateTrendingActors(trend.actors) 79 ··· 125 web(a.leading_snug), 126 ]} 127 numberOfLines={1}> 128 {category} 129 </Text> 130 </View>
+3 -24
src/state/queries/actor-starter-packs.ts
··· 1 - import { 2 - type AppBskyGraphGetActorStarterPacks, 3 - type AppBskyGraphGetStarterPacksWithMembership, 4 - } from '@atproto/api' 5 - import { 6 - type InfiniteData, 7 - type QueryClient, 8 - type QueryKey, 9 - useInfiniteQuery, 10 - } from '@tanstack/react-query' 11 12 import {useAgent} from '#/state/session' 13 ··· 28 }) { 29 const agent = useAgent() 30 31 - return useInfiniteQuery< 32 - AppBskyGraphGetActorStarterPacks.OutputSchema, 33 - Error, 34 - InfiniteData<AppBskyGraphGetActorStarterPacks.OutputSchema>, 35 - QueryKey, 36 - string | undefined 37 - >({ 38 queryKey: RQKEY(did), 39 queryFn: async ({pageParam}: {pageParam?: string}) => { 40 const res = await agent.app.bsky.graph.getActorStarterPacks({ ··· 59 }) { 60 const agent = useAgent() 61 62 - return useInfiniteQuery< 63 - AppBskyGraphGetStarterPacksWithMembership.OutputSchema, 64 - Error, 65 - InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema>, 66 - QueryKey, 67 - string | undefined 68 - >({ 69 queryKey: RQKEY_WITH_MEMBERSHIP(did), 70 queryFn: async ({pageParam}: {pageParam?: string}) => { 71 const res = await agent.app.bsky.graph.getStarterPacksWithMembership({
··· 1 + import {type QueryClient, useInfiniteQuery} from '@tanstack/react-query' 2 3 import {useAgent} from '#/state/session' 4 ··· 19 }) { 20 const agent = useAgent() 21 22 + return useInfiniteQuery({ 23 queryKey: RQKEY(did), 24 queryFn: async ({pageParam}: {pageParam?: string}) => { 25 const res = await agent.app.bsky.graph.getActorStarterPacks({ ··· 44 }) { 45 const agent = useAgent() 46 47 + return useInfiniteQuery({ 48 queryKey: RQKEY_WITH_MEMBERSHIP(did), 49 queryFn: async ({pageParam}: {pageParam?: string}) => { 50 const res = await agent.app.bsky.graph.getStarterPacksWithMembership({
+116 -2
src/state/queries/list-memberships.ts
··· 14 * -prf 15 */ 16 17 - import {AtUri} from '@atproto/api' 18 - import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 19 20 import {STALE} from '#/state/queries' 21 import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members' 22 import {useAgent, useSession} from '#/state/session' 23 24 // sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records 25 const SANITY_PAGE_LIMIT = 1000 ··· 91 } 92 93 export function useListMembershipAddMutation({ 94 onSuccess, 95 onError, 96 }: { 97 onSuccess?: (data: {uri: string; cid: string}) => void 98 onError?: (error: Error) => void 99 } = {}) { ··· 151 queryKey: LIST_MEMBERS_RQKEY(variables.listUri), 152 }) 153 }, 1e3) 154 onSuccess?.(data) 155 }, 156 onError, ··· 206 queryKey: LIST_MEMBERS_RQKEY(variables.listUri), 207 }) 208 }, 1e3) 209 onSuccess?.(data) 210 }, 211 onError,
··· 14 * -prf 15 */ 16 17 + import { 18 + type AppBskyActorDefs, 19 + type AppBskyGraphGetStarterPacksWithMembership, 20 + AtUri, 21 + } from '@atproto/api' 22 + import { 23 + type InfiniteData, 24 + useMutation, 25 + useQuery, 26 + useQueryClient, 27 + } from '@tanstack/react-query' 28 29 import {STALE} from '#/state/queries' 30 import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members' 31 import {useAgent, useSession} from '#/state/session' 32 + import type * as bsky from '#/types/bsky' 33 + import {RQKEY_WITH_MEMBERSHIP as STARTER_PACKS_WITH_MEMBERSHIPS_RKEY} from './actor-starter-packs' 34 35 // sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records 36 const SANITY_PAGE_LIMIT = 1000 ··· 102 } 103 104 export function useListMembershipAddMutation({ 105 + subject, 106 onSuccess, 107 onError, 108 }: { 109 + /** 110 + * Needed for optimistic update of starter pack query 111 + */ 112 + subject?: bsky.profile.AnyProfileView 113 onSuccess?: (data: {uri: string; cid: string}) => void 114 onError?: (error: Error) => void 115 } = {}) { ··· 167 queryKey: LIST_MEMBERS_RQKEY(variables.listUri), 168 }) 169 }, 1e3) 170 + 171 + // update WITH_MEMBERSHIPS query 172 + 173 + if (subject) { 174 + queryClient.setQueryData< 175 + InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema> 176 + >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { 177 + if (!old) return old 178 + 179 + return { 180 + ...old, 181 + pages: old.pages.map(page => ({ 182 + ...page, 183 + starterPacksWithMembership: page.starterPacksWithMembership.map( 184 + spWithMembership => { 185 + if ( 186 + spWithMembership.starterPack.list && 187 + spWithMembership.starterPack.list?.uri === variables.listUri 188 + ) { 189 + return { 190 + ...spWithMembership, 191 + starterPack: { 192 + ...spWithMembership.starterPack, 193 + listItemsSample: [ 194 + { 195 + uri: data.uri, 196 + subject: subject as AppBskyActorDefs.ProfileView, 197 + }, 198 + ...(spWithMembership.starterPack.listItemsSample?.filter( 199 + item => item.subject.did !== variables.actorDid, 200 + ) ?? []), 201 + ], 202 + list: { 203 + ...spWithMembership.starterPack.list, 204 + listItemCount: 205 + (spWithMembership.starterPack.list.listItemCount ?? 206 + 0) + 1, 207 + }, 208 + }, 209 + listItem: { 210 + uri: data.uri, 211 + subject: subject as AppBskyActorDefs.ProfileView, 212 + }, 213 + } 214 + } 215 + 216 + return spWithMembership 217 + }, 218 + ), 219 + })), 220 + } 221 + }) 222 + } 223 + 224 onSuccess?.(data) 225 }, 226 onError, ··· 276 queryKey: LIST_MEMBERS_RQKEY(variables.listUri), 277 }) 278 }, 1e3) 279 + 280 + queryClient.setQueryData< 281 + InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema> 282 + >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { 283 + if (!old) return old 284 + 285 + return { 286 + ...old, 287 + pages: old.pages.map(page => ({ 288 + ...page, 289 + starterPacksWithMembership: page.starterPacksWithMembership.map( 290 + spWithMembership => { 291 + if ( 292 + spWithMembership.starterPack.list && 293 + spWithMembership.starterPack.list.uri === variables.listUri 294 + ) { 295 + return { 296 + ...spWithMembership, 297 + starterPack: { 298 + ...spWithMembership.starterPack, 299 + listItemsSample: 300 + spWithMembership.starterPack.listItemsSample?.filter( 301 + item => item.subject.did !== variables.actorDid, 302 + ), 303 + list: { 304 + ...spWithMembership.starterPack.list, 305 + listItemCount: Math.max( 306 + 0, 307 + (spWithMembership.starterPack.list.listItemCount ?? 308 + 1) - 1, 309 + ), 310 + }, 311 + }, 312 + listItem: undefined, 313 + } 314 + } 315 + 316 + return spWithMembership 317 + }, 318 + ), 319 + })), 320 + } 321 + }) 322 + 323 onSuccess?.(data) 324 }, 325 onError,
+1 -1
src/state/session/index.tsx
··· 430 const {signinDialogControl} = useGlobalDialogsControlContext() 431 432 return useCallback( 433 - (fn: () => void) => { 434 if (hasSession) { 435 fn() 436 } else {
··· 430 const {signinDialogControl} = useGlobalDialogsControlContext() 431 432 return useCallback( 433 + (fn: () => unknown) => { 434 if (hasSession) { 435 fn() 436 } else {
+109 -31
src/view/com/composer/Composer.tsx
··· 45 import * as FileSystem from 'expo-file-system' 46 import {type ImagePickerAsset} from 'expo-image-picker' 47 import { 48 AppBskyUnspeccedDefs, 49 type AppBskyUnspeccedGetPostThreadV2, 50 AtUri, ··· 62 import {retry} from '#/lib/async/retry' 63 import {until} from '#/lib/async/until' 64 import { 65 MAX_GRAPHEME_LENGTH, 66 SUPPORTED_MIME_TYPES, 67 type SupportedMimeTypes, ··· 277 ) 278 279 const thread = composerState.thread 280 const activePost = thread.posts[composerState.activePostIndex] 281 const nextPost: PostDraft | undefined = 282 thread.posts[composerState.activePostIndex + 1] ··· 545 revokeAllMediaUrls() 546 }, [closeComposer, queryClient]) 547 548 const handleSaveDraft = React.useCallback(async () => { 549 const isNewDraft = !composerState.draftId 550 try { 551 const result = await saveDraft({ ··· 571 onClose() 572 } catch (e) { 573 logger.error('Failed to save draft', {error: e}) 574 - setError(_(msg`Failed to save draft`)) 575 } 576 - }, [saveDraft, composerState, composerDispatch, onClose, _, ax]) 577 578 // Save without closing - for use by DraftsButton 579 - const saveCurrentDraft = React.useCallback(async () => { 580 - const result = await saveDraft({ 581 - composerState, 582 - existingDraftId: composerState.draftId, 583 - }) 584 - composerDispatch({type: 'mark_saved', draftId: result.draftId}) 585 - }, [saveDraft, composerState, composerDispatch]) 586 587 // Handle discard action - fires metric and closes composer 588 const handleDiscard = React.useCallback(() => { ··· 1092 isEmpty={isComposerEmpty} 1093 isDirty={composerState.isDirty} 1094 isEditingDraft={!!composerState.draftId} 1095 textLength={thread.posts[0].richtext.text.length}> 1096 {missingAltError && <AltTextReminder error={missingAltError} />} 1097 <ErrorBanner ··· 1158 <Prompt.Outer control={discardPromptControl}> 1159 <Prompt.Content> 1160 <Prompt.TitleText> 1161 - {composerState.draftId ? ( 1162 - <Trans>Save changes?</Trans> 1163 ) : ( 1164 - <Trans>Save draft?</Trans> 1165 )} 1166 </Prompt.TitleText> 1167 <Prompt.DescriptionText> 1168 - {composerState.draftId ? ( 1169 - <Trans> 1170 - You have unsaved changes to this draft, would you like to 1171 - save them? 1172 - </Trans> 1173 ) : ( 1174 - <Trans> 1175 - Would you like to save this as a draft to edit later? 1176 - </Trans> 1177 )} 1178 </Prompt.DescriptionText> 1179 </Prompt.Content> 1180 <Prompt.Actions> 1181 - <Prompt.Action 1182 - cta={ 1183 - composerState.draftId 1184 - ? _(msg`Save changes`) 1185 - : _(msg`Save draft`) 1186 - } 1187 - onPress={handleSaveDraft} 1188 - color="primary" 1189 - /> 1190 <Prompt.Action 1191 cta={_(msg`Discard`)} 1192 onPress={handleDiscard} 1193 color="negative_subtle" 1194 /> 1195 - <Prompt.Cancel /> 1196 </Prompt.Actions> 1197 </Prompt.Outer> 1198 )} ··· 1422 isEmpty, 1423 isDirty, 1424 isEditingDraft, 1425 textLength, 1426 topBarAnimatedStyle, 1427 children, ··· 1435 onCancel: () => void 1436 onPublish: () => void 1437 onSelectDraft: (draft: DraftSummary) => void 1438 - onSaveDraft: () => Promise<void> 1439 onDiscard: () => void 1440 isEmpty: boolean 1441 isDirty: boolean 1442 isEditingDraft: boolean 1443 textLength: number 1444 topBarAnimatedStyle: StyleProp<ViewStyle> 1445 children?: React.ReactNode ··· 1488 isEmpty={isEmpty} 1489 isDirty={isDirty} 1490 isEditingDraft={isEditingDraft} 1491 textLength={textLength} 1492 /> 1493 )}
··· 45 import * as FileSystem from 'expo-file-system' 46 import {type ImagePickerAsset} from 'expo-image-picker' 47 import { 48 + AppBskyDraftCreateDraft, 49 AppBskyUnspeccedDefs, 50 type AppBskyUnspeccedGetPostThreadV2, 51 AtUri, ··· 63 import {retry} from '#/lib/async/retry' 64 import {until} from '#/lib/async/until' 65 import { 66 + MAX_DRAFT_GRAPHEME_LENGTH, 67 MAX_GRAPHEME_LENGTH, 68 SUPPORTED_MIME_TYPES, 69 type SupportedMimeTypes, ··· 279 ) 280 281 const thread = composerState.thread 282 + 283 + // Clear error when composer content changes, but only if all posts are 284 + // back within the character limit. 285 + const allPostsWithinLimit = thread.posts.every( 286 + post => post.richtext.graphemeLength <= MAX_DRAFT_GRAPHEME_LENGTH, 287 + ) 288 + 289 const activePost = thread.posts[composerState.activePostIndex] 290 const nextPost: PostDraft | undefined = 291 thread.posts[composerState.activePostIndex + 1] ··· 554 revokeAllMediaUrls() 555 }, [closeComposer, queryClient]) 556 557 + const getDraftSaveError = React.useCallback( 558 + (e: unknown): string => { 559 + if (e instanceof AppBskyDraftCreateDraft.DraftLimitReachedError) { 560 + return _(msg`You've reached the maximum number of drafts`) 561 + } 562 + return _(msg`Failed to save draft`) 563 + }, 564 + [_], 565 + ) 566 + 567 + const validateDraftTextOrError = React.useCallback((): boolean => { 568 + const tooLong = composerState.thread.posts.some( 569 + post => post.richtext.graphemeLength > MAX_DRAFT_GRAPHEME_LENGTH, 570 + ) 571 + if (tooLong) { 572 + setError( 573 + _( 574 + msg`One or more posts are too long to save as a draft. ${plural(MAX_DRAFT_GRAPHEME_LENGTH, {one: 'The maximum number of characters is # character.', other: 'The maximum number of characters is # characters.'})}`, 575 + ), 576 + ) 577 + return false 578 + } 579 + return true 580 + }, [composerState.thread.posts, _]) 581 + 582 const handleSaveDraft = React.useCallback(async () => { 583 + setError('') 584 + if (!validateDraftTextOrError()) { 585 + return 586 + } 587 const isNewDraft = !composerState.draftId 588 try { 589 const result = await saveDraft({ ··· 609 onClose() 610 } catch (e) { 611 logger.error('Failed to save draft', {error: e}) 612 + setError(getDraftSaveError(e)) 613 } 614 + }, [ 615 + saveDraft, 616 + composerState, 617 + composerDispatch, 618 + onClose, 619 + ax, 620 + validateDraftTextOrError, 621 + getDraftSaveError, 622 + ]) 623 624 // Save without closing - for use by DraftsButton 625 + const saveCurrentDraft = React.useCallback(async (): Promise<{ 626 + success: boolean 627 + }> => { 628 + setError('') 629 + if (!validateDraftTextOrError()) { 630 + return {success: false} 631 + } 632 + try { 633 + const result = await saveDraft({ 634 + composerState, 635 + existingDraftId: composerState.draftId, 636 + }) 637 + composerDispatch({type: 'mark_saved', draftId: result.draftId}) 638 + return {success: true} 639 + } catch (e) { 640 + setError(getDraftSaveError(e)) 641 + return {success: false} 642 + } 643 + }, [ 644 + saveDraft, 645 + composerState, 646 + composerDispatch, 647 + validateDraftTextOrError, 648 + getDraftSaveError, 649 + ]) 650 651 // Handle discard action - fires metric and closes composer 652 const handleDiscard = React.useCallback(() => { ··· 1156 isEmpty={isComposerEmpty} 1157 isDirty={composerState.isDirty} 1158 isEditingDraft={!!composerState.draftId} 1159 + canSaveDraft={allPostsWithinLimit} 1160 textLength={thread.posts[0].richtext.text.length}> 1161 {missingAltError && <AltTextReminder error={missingAltError} />} 1162 <ErrorBanner ··· 1223 <Prompt.Outer control={discardPromptControl}> 1224 <Prompt.Content> 1225 <Prompt.TitleText> 1226 + {allPostsWithinLimit ? ( 1227 + composerState.draftId ? ( 1228 + <Trans>Save changes?</Trans> 1229 + ) : ( 1230 + <Trans>Save draft?</Trans> 1231 + ) 1232 ) : ( 1233 + <Trans>Discard post?</Trans> 1234 )} 1235 </Prompt.TitleText> 1236 <Prompt.DescriptionText> 1237 + {allPostsWithinLimit ? ( 1238 + composerState.draftId ? ( 1239 + <Trans> 1240 + You have unsaved changes to this draft, would you like to 1241 + save them? 1242 + </Trans> 1243 + ) : ( 1244 + <Trans> 1245 + Would you like to save this as a draft to edit later? 1246 + </Trans> 1247 + ) 1248 ) : ( 1249 + <Trans>You can only save drafts up to 1000 characters.</Trans> 1250 )} 1251 </Prompt.DescriptionText> 1252 </Prompt.Content> 1253 <Prompt.Actions> 1254 + {allPostsWithinLimit && ( 1255 + <Prompt.Action 1256 + cta={ 1257 + composerState.draftId 1258 + ? _(msg`Save changes`) 1259 + : _(msg`Save draft`) 1260 + } 1261 + onPress={handleSaveDraft} 1262 + color="primary" 1263 + /> 1264 + )} 1265 <Prompt.Action 1266 cta={_(msg`Discard`)} 1267 onPress={handleDiscard} 1268 color="negative_subtle" 1269 /> 1270 + <Prompt.Cancel cta={_(msg`Keep editing`)} /> 1271 </Prompt.Actions> 1272 </Prompt.Outer> 1273 )} ··· 1497 isEmpty, 1498 isDirty, 1499 isEditingDraft, 1500 + canSaveDraft, 1501 textLength, 1502 topBarAnimatedStyle, 1503 children, ··· 1511 onCancel: () => void 1512 onPublish: () => void 1513 onSelectDraft: (draft: DraftSummary) => void 1514 + onSaveDraft: () => Promise<{success: boolean}> 1515 onDiscard: () => void 1516 isEmpty: boolean 1517 isDirty: boolean 1518 isEditingDraft: boolean 1519 + canSaveDraft: boolean 1520 textLength: number 1521 topBarAnimatedStyle: StyleProp<ViewStyle> 1522 children?: React.ReactNode ··· 1565 isEmpty={isEmpty} 1566 isDirty={isDirty} 1567 isEditingDraft={isEditingDraft} 1568 + canSaveDraft={canSaveDraft} 1569 textLength={textLength} 1570 /> 1571 )}
+1
src/view/com/composer/drafts/DraftItem.tsx
··· 98 {!!post.text.trim().length && ( 99 <RichText 100 style={[a.text_md, a.leading_snug, a.pointer_events_none]} 101 value={post.text} 102 enableTags 103 disableMentionFacetValidation
··· 98 {!!post.text.trim().length && ( 99 <RichText 100 style={[a.text_md, a.leading_snug, a.pointer_events_none]} 101 + numberOfLines={8} 102 value={post.text} 103 enableTags 104 disableMentionFacetValidation
+36 -18
src/view/com/composer/drafts/DraftsButton.tsx
··· 18 isEmpty, 19 isDirty, 20 isEditingDraft, 21 textLength, 22 }: { 23 onSelectDraft: (draft: DraftSummary) => void 24 - onSaveDraft: () => Promise<void> 25 onDiscard: () => void 26 isEmpty: boolean 27 isDirty: boolean 28 isEditingDraft: boolean 29 textLength: number 30 }) { 31 const {_} = useLingui() ··· 46 } 47 48 const handleSaveAndOpen = async () => { 49 - await onSaveDraft() 50 - draftsDialogControl.open() 51 } 52 53 const handleDiscardAndOpen = () => { ··· 90 <Prompt.Outer control={savePromptControl}> 91 <Prompt.Content> 92 <Prompt.TitleText> 93 - {isEditingDraft ? ( 94 - <Trans>Save changes?</Trans> 95 ) : ( 96 - <Trans>Save draft?</Trans> 97 )} 98 </Prompt.TitleText> 99 </Prompt.Content> 100 <Prompt.DescriptionText> 101 - {isEditingDraft ? ( 102 - <Trans> 103 - You have unsaved changes. Would you like to save them before 104 - viewing your drafts? 105 - </Trans> 106 ) : ( 107 <Trans> 108 - Would you like to save this as a draft before viewing your drafts? 109 </Trans> 110 )} 111 </Prompt.DescriptionText> 112 <Prompt.Actions> 113 - <Prompt.Action 114 - cta={isEditingDraft ? _(msg`Save changes`) : _(msg`Save draft`)} 115 - onPress={handleSaveAndOpen} 116 - color="primary" 117 - /> 118 <Prompt.Action 119 cta={_(msg`Discard`)} 120 onPress={handleDiscardAndOpen} 121 color="negative_subtle" 122 /> 123 - <Prompt.Cancel /> 124 </Prompt.Actions> 125 </Prompt.Outer> 126 </>
··· 18 isEmpty, 19 isDirty, 20 isEditingDraft, 21 + canSaveDraft, 22 textLength, 23 }: { 24 onSelectDraft: (draft: DraftSummary) => void 25 + onSaveDraft: () => Promise<{success: boolean}> 26 onDiscard: () => void 27 isEmpty: boolean 28 isDirty: boolean 29 isEditingDraft: boolean 30 + canSaveDraft: boolean 31 textLength: number 32 }) { 33 const {_} = useLingui() ··· 48 } 49 50 const handleSaveAndOpen = async () => { 51 + const {success} = await onSaveDraft() 52 + if (success) { 53 + draftsDialogControl.open() 54 + } 55 } 56 57 const handleDiscardAndOpen = () => { ··· 94 <Prompt.Outer control={savePromptControl}> 95 <Prompt.Content> 96 <Prompt.TitleText> 97 + {canSaveDraft ? ( 98 + isEditingDraft ? ( 99 + <Trans>Save changes?</Trans> 100 + ) : ( 101 + <Trans>Save draft?</Trans> 102 + ) 103 ) : ( 104 + <Trans>Discard draft?</Trans> 105 )} 106 </Prompt.TitleText> 107 </Prompt.Content> 108 <Prompt.DescriptionText> 109 + {canSaveDraft ? ( 110 + isEditingDraft ? ( 111 + <Trans> 112 + You have unsaved changes. Would you like to save them before 113 + viewing your drafts? 114 + </Trans> 115 + ) : ( 116 + <Trans> 117 + Would you like to save this as a draft before viewing your 118 + drafts? 119 + </Trans> 120 + ) 121 ) : ( 122 <Trans> 123 + You can only save drafts up to 1000 characters. Would you like to 124 + discard this post before viewing your drafts? 125 </Trans> 126 )} 127 </Prompt.DescriptionText> 128 <Prompt.Actions> 129 + {canSaveDraft && ( 130 + <Prompt.Action 131 + cta={isEditingDraft ? _(msg`Save changes`) : _(msg`Save draft`)} 132 + onPress={handleSaveAndOpen} 133 + color="primary" 134 + /> 135 + )} 136 <Prompt.Action 137 cta={_(msg`Discard`)} 138 onPress={handleDiscardAndOpen} 139 color="negative_subtle" 140 /> 141 + <Prompt.Cancel cta={_(msg`Keep editing`)} /> 142 </Prompt.Actions> 143 </Prompt.Outer> 144 </>
+9 -6
src/view/com/composer/drafts/DraftsListDialog.tsx
··· 1 import {useCallback, useEffect, useMemo} from 'react' 2 - import {View} from 'react-native' 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import {useCallOnce} from '#/lib/once' 7 import {EmptyState} from '#/view/com/util/EmptyState' 8 - import {atoms as a, select, useBreakpoints, useTheme, web} from '#/alf' 9 import {Button, ButtonText} from '#/components/Button' 10 import * as Dialog from '#/components/Dialog' 11 import {PageX_Stroke2_Corner0_Rounded_Large as PageXIcon} from '#/components/icons/PageX' ··· 54 55 const handleSelectDraft = useCallback( 56 (summary: DraftSummary) => { 57 control.close(() => { 58 onSelectDraft(summary) 59 }) ··· 173 ListFooterComponent={footerComponent} 174 onEndReached={onEndReached} 175 onEndReachedThreshold={0.5} 176 - style={[ 177 - a.px_0, 178 - web({minHeight: 500}), 179 - ]} 180 webInnerContentContainerStyle={[a.py_0]} 181 contentContainerStyle={[a.pb_xl]} 182 />
··· 1 import {useCallback, useEffect, useMemo} from 'react' 2 + import {Keyboard, View} from 'react-native' 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' 5 6 import {useCallOnce} from '#/lib/once' 7 import {EmptyState} from '#/view/com/util/EmptyState' 8 + import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 9 import {Button, ButtonText} from '#/components/Button' 10 import * as Dialog from '#/components/Dialog' 11 import {PageX_Stroke2_Corner0_Rounded_Large as PageXIcon} from '#/components/icons/PageX' ··· 54 55 const handleSelectDraft = useCallback( 56 (summary: DraftSummary) => { 57 + // Dismiss keyboard immediately to prevent flicker. Without this, 58 + // the text input regains focus (showing the keyboard) after the 59 + // drafts sheet closes, then loses it again when the post component 60 + // remounts with the draft content, causing a show-hide-show cycle -sfn 61 + Keyboard.dismiss() 62 + 63 control.close(() => { 64 onSelectDraft(summary) 65 }) ··· 179 ListFooterComponent={footerComponent} 180 onEndReached={onEndReached} 181 onEndReachedThreshold={0.5} 182 + style={[a.px_0, web({minHeight: 500})]} 183 webInnerContentContainerStyle={[a.py_0]} 184 contentContainerStyle={[a.pb_xl]} 185 />
+11 -9
src/view/com/profile/ProfileMenu.tsx
··· 383 )} 384 </> 385 )} 386 - <Menu.Item 387 - testID="profileHeaderDropdownStarterPackAddRemoveBtn" 388 - label={_(msg`Add to starter packs`)} 389 - onPress={onPressAddToStarterPacks}> 390 - <Menu.ItemText> 391 - <Trans>Add to starter packs</Trans> 392 - </Menu.ItemText> 393 - <Menu.ItemIcon icon={StarterPack} /> 394 - </Menu.Item> 395 <Menu.Item 396 testID="profileHeaderDropdownListAddRemoveBtn" 397 label={_(msg`Add to lists`)}
··· 383 )} 384 </> 385 )} 386 + {!isSelf && ( 387 + <Menu.Item 388 + testID="profileHeaderDropdownStarterPackAddRemoveBtn" 389 + label={_(msg`Add to starter packs`)} 390 + onPress={onPressAddToStarterPacks}> 391 + <Menu.ItemText> 392 + <Trans>Add to starter packs</Trans> 393 + </Menu.ItemText> 394 + <Menu.ItemIcon icon={StarterPack} /> 395 + </Menu.Item> 396 + )} 397 <Menu.Item 398 testID="profileHeaderDropdownListAddRemoveBtn" 399 label={_(msg`Add to lists`)}
+27 -27
src/view/screens/Profile.tsx
··· 1 - import React, {useCallback, useMemo} from 'react' 2 import {StyleSheet} from 'react-native' 3 import {SafeAreaView} from 'react-native-safe-area-context' 4 import { ··· 79 data: resolvedDid, 80 error: resolveError, 81 refetch: refetchDid, 82 - isLoading: isLoadingDid, 83 } = useResolveDidQuery(name) 84 const { 85 data: profile, 86 error: profileError, 87 refetch: refetchProfile, 88 - isLoading: isLoadingProfile, 89 isPlaceholderData: isPlaceholderProfile, 90 } = useProfileQuery({ 91 did: resolvedDid, 92 }) 93 94 - const onPressTryAgain = React.useCallback(() => { 95 if (resolveError) { 96 - refetchDid() 97 } else { 98 - refetchProfile() 99 } 100 }, [resolveError, refetchDid, refetchProfile]) 101 102 // Apply hard-coded redirects as need 103 - React.useEffect(() => { 104 if (resolveError) { 105 if (name === 'lulaoficial.bsky.social') { 106 console.log('Applying redirect to lula.com.br') 107 - navigate('Profile', {name: 'lula.com.br'}) 108 } 109 } 110 }, [name, resolveError]) 111 112 // When we open the profile, we want to reset the posts query if we are blocked. 113 - React.useEffect(() => { 114 if (resolvedDid && profile?.viewer?.blockedBy) { 115 resetProfilePostsQueries(queryClient, resolvedDid) 116 } 117 }, [queryClient, profile?.viewer?.blockedBy, resolvedDid]) 118 119 // Most pushes will happen here, since we will have only placeholder data 120 - if (isLoadingDid || isLoadingProfile) { 121 return ( 122 <Layout.Content> 123 <ProfileHeaderLoading /> ··· 186 did: profile.did, 187 enabled: !!profile.associated?.labeler, 188 }) 189 - const [currentPage, setCurrentPage] = React.useState(0) 190 const {_} = useLingui() 191 192 - const [scrollViewTag, setScrollViewTag] = React.useState<number | null>(null) 193 194 - const postsSectionRef = React.useRef<SectionRef>(null) 195 - const repliesSectionRef = React.useRef<SectionRef>(null) 196 - const mediaSectionRef = React.useRef<SectionRef>(null) 197 - const videosSectionRef = React.useRef<SectionRef>(null) 198 - const likesSectionRef = React.useRef<SectionRef>(null) 199 - const feedsSectionRef = React.useRef<SectionRef>(null) 200 - const listsSectionRef = React.useRef<SectionRef>(null) 201 - const starterPacksSectionRef = React.useRef<SectionRef>(null) 202 - const labelsSectionRef = React.useRef<SectionRef>(null) 203 204 useSetTitle(combinedDisplayName(profile)) 205 ··· 315 ) 316 317 useFocusEffect( 318 - React.useCallback(() => { 319 setMinimalShellMode(false) 320 return listenSoftReset(() => { 321 scrollSectionToTop(currentPage) ··· 601 602 function useRichText(text: string): [RichTextAPI, boolean] { 603 const agent = useAgent() 604 - const [prevText, setPrevText] = React.useState(text) 605 - const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text})) 606 - const [resolvedRT, setResolvedRT] = React.useState<RichTextAPI | null>(null) 607 if (text !== prevText) { 608 setPrevText(text) 609 setRawRT(new RichTextAPI({text})) 610 setResolvedRT(null) 611 // This will queue an immediate re-render 612 } 613 - React.useEffect(() => { 614 let ignore = false 615 async function resolveRTFacets() { 616 // new each time ··· 620 setResolvedRT(resolvedRT) 621 } 622 } 623 - resolveRTFacets() 624 return () => { 625 ignore = true 626 }
··· 1 + import {useCallback, useEffect, useMemo, useRef, useState} from 'react' 2 import {StyleSheet} from 'react-native' 3 import {SafeAreaView} from 'react-native-safe-area-context' 4 import { ··· 79 data: resolvedDid, 80 error: resolveError, 81 refetch: refetchDid, 82 + isPending: isDidPending, 83 } = useResolveDidQuery(name) 84 const { 85 data: profile, 86 error: profileError, 87 refetch: refetchProfile, 88 isPlaceholderData: isPlaceholderProfile, 89 + isPending: isProfilePending, 90 } = useProfileQuery({ 91 did: resolvedDid, 92 }) 93 94 + const onPressTryAgain = useCallback(() => { 95 if (resolveError) { 96 + void refetchDid() 97 } else { 98 + void refetchProfile() 99 } 100 }, [resolveError, refetchDid, refetchProfile]) 101 102 // Apply hard-coded redirects as need 103 + useEffect(() => { 104 if (resolveError) { 105 if (name === 'lulaoficial.bsky.social') { 106 console.log('Applying redirect to lula.com.br') 107 + void navigate('Profile', {name: 'lula.com.br'}) 108 } 109 } 110 }, [name, resolveError]) 111 112 // When we open the profile, we want to reset the posts query if we are blocked. 113 + useEffect(() => { 114 if (resolvedDid && profile?.viewer?.blockedBy) { 115 resetProfilePostsQueries(queryClient, resolvedDid) 116 } 117 }, [queryClient, profile?.viewer?.blockedBy, resolvedDid]) 118 119 // Most pushes will happen here, since we will have only placeholder data 120 + if (isDidPending || isProfilePending) { 121 return ( 122 <Layout.Content> 123 <ProfileHeaderLoading /> ··· 186 did: profile.did, 187 enabled: !!profile.associated?.labeler, 188 }) 189 + const [currentPage, setCurrentPage] = useState(0) 190 const {_} = useLingui() 191 192 + const [scrollViewTag, setScrollViewTag] = useState<number | null>(null) 193 194 + const postsSectionRef = useRef<SectionRef>(null) 195 + const repliesSectionRef = useRef<SectionRef>(null) 196 + const mediaSectionRef = useRef<SectionRef>(null) 197 + const videosSectionRef = useRef<SectionRef>(null) 198 + const likesSectionRef = useRef<SectionRef>(null) 199 + const feedsSectionRef = useRef<SectionRef>(null) 200 + const listsSectionRef = useRef<SectionRef>(null) 201 + const starterPacksSectionRef = useRef<SectionRef>(null) 202 + const labelsSectionRef = useRef<SectionRef>(null) 203 204 useSetTitle(combinedDisplayName(profile)) 205 ··· 315 ) 316 317 useFocusEffect( 318 + useCallback(() => { 319 setMinimalShellMode(false) 320 return listenSoftReset(() => { 321 scrollSectionToTop(currentPage) ··· 601 602 function useRichText(text: string): [RichTextAPI, boolean] { 603 const agent = useAgent() 604 + const [prevText, setPrevText] = useState(text) 605 + const [rawRT, setRawRT] = useState(() => new RichTextAPI({text})) 606 + const [resolvedRT, setResolvedRT] = useState<RichTextAPI | null>(null) 607 if (text !== prevText) { 608 setPrevText(text) 609 setRawRT(new RichTextAPI({text})) 610 setResolvedRT(null) 611 // This will queue an immediate re-render 612 } 613 + useEffect(() => { 614 let ignore = false 615 async function resolveRTFacets() { 616 // new each time ··· 620 setResolvedRT(resolvedRT) 621 } 622 } 623 + void resolveRTFacets() 624 return () => { 625 ignore = true 626 }
+4 -11
web/index.html
··· 149 </noscript> 150 151 <!-- The root element for your Expo app. --> 152 <div id="root"> 153 - <div id="splash"> 154 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> 155 - 156 - <defs> 157 - <linearGradient x1="0" y1="0" x2="0" y2="1" id="index_sky"><stop offset="0" stop-color="#ED5345" stop-opacity="1"></stop><stop offset="1" stop-color="#E25C50" stop-opacity="1"></stop></linearGradient> 158 - </defs> 159 - <path fill="url(#index_sky)" 160 - d="M374.473 57.7173C367.666 50.7995 357.119 49.1209 348.441 53.1659C347.173 53.7567 342.223 56.0864 334.796 59.8613C326.32 64.1696 314.568 70.3869 301.394 78.0596C275.444 93.1728 242.399 114.83 218.408 139.477C185.983 172.786 158.719 225.503 140.029 267.661C130.506 289.144 122.878 308.661 117.629 322.81C116.301 326.389 115.124 329.63 114.104 332.478C87.1783 336.42 64.534 341.641 47.5078 348.101C37.6493 351.84 28.3222 356.491 21.0573 362.538C13.8818 368.511 6.00003 378.262 6.00003 391.822C6.00014 403.222 11.8738 411.777 17.4566 417.235C23.0009 422.655 29.9593 426.793 36.871 430.062C50.8097 436.653 69.5275 441.988 90.8362 446.249C133.828 454.846 192.21 460 256.001 460C319.79 460 378.172 454.846 421.164 446.249C442.472 441.988 461.19 436.653 475.129 430.062C482.041 426.793 488.999 422.655 494.543 417.235C500.039 411.862 505.817 403.489 505.996 392.353L506 391.822L505.995 391.188C505.754 377.959 498.012 368.417 490.945 362.534C483.679 356.485 474.35 351.835 464.491 348.095C446.749 341.366 422.906 335.982 394.476 331.987C393.6 330.57 392.633 328.995 391.595 327.273C386.477 318.777 379.633 306.842 372.737 293.115C358.503 264.781 345.757 232.098 344.756 206.636C343.87 184.121 351.638 154.087 360.819 127.789C365.27 115.041 369.795 103.877 373.207 95.9072C374.909 91.9309 376.325 88.7712 377.302 86.6328C377.79 85.5645 378.167 84.7524 378.416 84.2224C378.54 83.9579 378.632 83.7635 378.69 83.643C378.718 83.5829 378.739 83.5411 378.75 83.5181C378.753 83.5108 378.756 83.5049 378.757 83.5015C382.909 74.8634 381.196 64.5488 374.473 57.7173Z" 161 - /> 162 - </svg> 163 - </div> 164 </div> 165 </body> 166 </html>
··· 149 </noscript> 150 151 <!-- The root element for your Expo app. --> 152 + <div id="splash"> 153 + <!-- Witchsky SVG --> 154 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#ED5345" d="M374.473 57.7173C367.666 50.7995 357.119 49.1209 348.441 53.1659C347.173 53.7567 342.223 56.0864 334.796 59.8613C326.32 64.1696 314.568 70.3869 301.394 78.0596C275.444 93.1728 242.399 114.83 218.408 139.477C185.983 172.786 158.719 225.503 140.029 267.661C130.506 289.144 122.878 308.661 117.629 322.81C116.301 326.389 115.124 329.63 114.104 332.478C87.1783 336.42 64.534 341.641 47.5078 348.101C37.6493 351.84 28.3222 356.491 21.0573 362.538C13.8818 368.511 6.00003 378.262 6.00003 391.822C6.00014 403.222 11.8738 411.777 17.4566 417.235C23.0009 422.655 29.9593 426.793 36.871 430.062C50.8097 436.653 69.5275 441.988 90.8362 446.249C133.828 454.846 192.21 460 256.001 460C319.79 460 378.172 454.846 421.164 446.249C442.472 441.988 461.19 436.653 475.129 430.062C482.041 426.793 488.999 422.655 494.543 417.235C500.039 411.862 505.817 403.489 505.996 392.353L506 391.822L505.995 391.188C505.754 377.959 498.012 368.417 490.945 362.534C483.679 356.485 474.35 351.835 464.491 348.095C446.749 341.366 422.906 335.982 394.476 331.987C393.6 330.57 392.633 328.995 391.595 327.273C386.477 318.777 379.633 306.842 372.737 293.115C358.503 264.781 345.757 232.098 344.756 206.636C343.87 184.121 351.638 154.087 360.819 127.789C365.27 115.041 369.795 103.877 373.207 95.9072C374.909 91.9309 376.325 88.7712 377.302 86.6328C377.79 85.5645 378.167 84.7524 378.416 84.2224C378.54 83.9579 378.632 83.7635 378.69 83.643C378.718 83.5829 378.739 83.5411 378.75 83.5181C378.753 83.5108 378.756 83.5049 378.757 83.5015C382.909 74.8634 381.196 64.5488 374.473 57.7173Z"/></svg> 155 + </div> 156 <div id="root"> 157 </div> 158 </body> 159 </html>
+160 -115
yarn.lock
··· 82 "@atproto/xrpc" "^0.7.6" 83 "@atproto/xrpc-server" "^0.10.0" 84 85 - "@atproto/api@^0.18.18": 86 - version "0.18.18" 87 - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.18.tgz#0c02585ea1b0f4a37444b0d8dfbe347c99ea5f64" 88 - integrity sha512-Vg7/sjbwDQZDj8fXtb4E48U4gA+6RC1iSt7onGnH2NyR0E25uds1KnqSKMzqcphdJXrz5GXrgHWc747XPGibzg== 89 dependencies: 90 - "@atproto/common-web" "^0.4.14" 91 "@atproto/lexicon" "^0.6.1" 92 "@atproto/syntax" "^0.4.3" 93 "@atproto/xrpc" "^0.7.7" ··· 96 tlds "^1.234.0" 97 zod "^3.23.8" 98 99 - "@atproto/api@^0.18.19", "@atproto/api@^0.18.20": 100 - version "0.18.20" 101 - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.20.tgz#3fdbb7b7fae90bd59101970c2b56cc31e8cf417d" 102 - integrity sha512-BZYZkh2VJIFCXEnc/vzKwAwWjAQQTgbNJ8FBxpBK+z+KYh99O0uPCsRYKoCQsRrnkgrhzdU9+g2G+7zanTIGbw== 103 dependencies: 104 - "@atproto/common-web" "^0.4.15" 105 "@atproto/lexicon" "^0.6.1" 106 "@atproto/syntax" "^0.4.3" 107 "@atproto/xrpc" "^0.7.7" ··· 128 multiformats "^9.9.0" 129 uint8arrays "3.0.0" 130 131 - "@atproto/bsky@^0.0.214": 132 - version "0.0.214" 133 - resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.214.tgz#54183957e323fe0800606acdcc3137acadf6672e" 134 - integrity sha512-Fn+o3WcaX57EmcLoUdcViY4wU2UzfbyNlS/qFQu+1clADtL3pthylwTJgwUU73cZE+7MILZPfTzhQTc15snR9A== 135 dependencies: 136 "@atproto-labs/fetch-node" "^0.2.0" 137 "@atproto-labs/xrpc-utils" "^0.0.24" 138 - "@atproto/api" "^0.18.20" 139 - "@atproto/common" "^0.5.10" 140 "@atproto/crypto" "^0.4.5" 141 "@atproto/did" "^0.3.0" 142 - "@atproto/identity" "^0.4.10" 143 "@atproto/lexicon" "^0.6.1" 144 "@atproto/repo" "^0.8.12" 145 "@atproto/sync" "^0.1.39" 146 "@atproto/syntax" "^0.4.3" 147 - "@atproto/xrpc-server" "^0.10.11" 148 "@bufbuild/protobuf" "^1.5.0" 149 "@connectrpc/connect" "^1.1.4" 150 "@connectrpc/connect-express" "^1.1.4" ··· 194 pino-http "^8.2.1" 195 typed-emitter "^2.1.0" 196 197 - "@atproto/common-web@^0.4.13", "@atproto/common-web@^0.4.14", "@atproto/common-web@^0.4.4", "@atproto/common-web@^0.4.7": 198 version "0.4.14" 199 resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.14.tgz#77545d454add08d735af00ffc29dd6abf1ab77b3" 200 integrity sha512-rMU8Q+kpyPpirUS9OqT7aOD1hxKa+diem3vc7BA0lOkj0tU6wcAxegxmbPZ8JaOsR7SSYhP/jCt/5wbT4qqkuQ== ··· 214 "@atproto/syntax" "^0.4.3" 215 zod "^3.23.8" 216 217 "@atproto/common@0.1.0": 218 version "0.1.0" 219 resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" ··· 246 multiformats "^9.9.0" 247 pino "^8.21.0" 248 249 - "@atproto/common@^0.5.10": 250 - version "0.5.10" 251 - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.10.tgz#f0d6b7e012b3dd3991d7e2975e26913d4f41e90f" 252 - integrity sha512-A1+4W3JmjZIgmtJFLJBAaoVruZhRL0ANtyjZ91aJR4rjHcZuaQ+v4IFR1UcE6yyTATacLdBk6ADy8OtxXzq14g== 253 dependencies: 254 - "@atproto/common-web" "^0.4.15" 255 - "@atproto/lex-cbor" "^0.0.10" 256 - "@atproto/lex-data" "^0.0.10" 257 iso-datestring-validator "^2.2.2" 258 multiformats "^9.9.0" 259 pino "^8.21.0" ··· 278 "@noble/hashes" "^1.6.1" 279 uint8arrays "3.0.0" 280 281 - "@atproto/dev-env@^0.3.208": 282 - version "0.3.208" 283 - resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.208.tgz#01d07d4b1ef2404adb07bdddf324393989231a52" 284 - integrity sha512-ttx+MIT21iFICW7sZNaFJoCPlxZAqgFy1ztOEn1ebW2MkJzsNHSq2oQC8MHxmpbYEOMPekTXC2iMHW6PYVQJxQ== 285 dependencies: 286 - "@atproto/api" "^0.18.20" 287 - "@atproto/bsky" "^0.0.214" 288 "@atproto/bsync" "^0.0.23" 289 - "@atproto/common-web" "^0.4.15" 290 "@atproto/crypto" "^0.4.5" 291 - "@atproto/identity" "^0.4.10" 292 "@atproto/lexicon" "^0.6.1" 293 - "@atproto/ozone" "^0.1.162" 294 - "@atproto/pds" "^0.4.207" 295 "@atproto/sync" "^0.1.39" 296 "@atproto/syntax" "^0.4.3" 297 - "@atproto/xrpc-server" "^0.10.11" 298 "@did-plc/lib" "^0.0.1" 299 "@did-plc/server" "^0.0.1" 300 dotenv "^16.0.3" ··· 319 "@atproto/common-web" "^0.4.4" 320 "@atproto/crypto" "^0.4.4" 321 322 "@atproto/jwk-jose@^0.1.11": 323 version "0.1.11" 324 resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz#ef64bce940a66e267fc3cf0db8df4dbd062bb28a" ··· 343 "@atproto/lex-data" "0.0.9" 344 tslib "^2.8.1" 345 346 - "@atproto/lex-cbor@^0.0.10": 347 - version "0.0.10" 348 - resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz#712abc4fedef4854c341e32d0fd1608e45616984" 349 - integrity sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA== 350 dependencies: 351 - "@atproto/lex-data" "^0.0.10" 352 tslib "^2.8.1" 353 354 - "@atproto/lex-client@^0.0.11": 355 - version "0.0.11" 356 - resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.11.tgz#b8e8d0da81ed27f1093d0a1e1c7fdb994394e323" 357 - integrity sha512-2DCidAlhATtZc1Z11PUd+C98BiW/Od4pWtDlQSAxkjHSC/56ZwuSkZQVx27ISk1HldfKVc9qUvQWA9nhmrxYIw== 358 dependencies: 359 - "@atproto/lex-data" "^0.0.10" 360 - "@atproto/lex-json" "^0.0.10" 361 - "@atproto/lex-schema" "^0.0.11" 362 tslib "^2.8.1" 363 364 "@atproto/lex-data@0.0.9": ··· 381 uint8arrays "3.0.0" 382 unicode-segmenter "^0.14.0" 383 384 - "@atproto/lex-document@^0.0.12": 385 - version "0.0.12" 386 - resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.12.tgz#18e0ca344101bb742a2930f663473e0213721f52" 387 - integrity sha512-+no+ZXyCNdOdjkOj6a4n4WHAQzZz3M6VJTwx7IQQC2+to41/4fFr6k8U1y3Jtq2lYcbHqapkJj3RGIxzFcrtwA== 388 dependencies: 389 - "@atproto/lex-schema" "^0.0.11" 390 core-js "^3" 391 tslib "^2.8.1" 392 ··· 406 "@atproto/lex-data" "^0.0.10" 407 tslib "^2.8.1" 408 409 - "@atproto/lex-resolver@^0.0.13": 410 - version "0.0.13" 411 - resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.13.tgz#6407b813001d3a3f9e4048197755deccb3cc6f62" 412 - integrity sha512-CcqCE6W3ZVMVzAihatpVbXLxO15mtvddzRzovIJ9QxBTpUmtkdmIk9/gle0oAsRToTJYQy2a6dwAmnAosxP/XQ== 413 dependencies: 414 "@atproto-labs/did-resolver" "^0.2.6" 415 "@atproto/crypto" "^0.4.5" 416 - "@atproto/lex-client" "^0.0.11" 417 - "@atproto/lex-data" "^0.0.10" 418 - "@atproto/lex-document" "^0.0.12" 419 - "@atproto/lex-schema" "^0.0.11" 420 "@atproto/repo" "^0.8.12" 421 "@atproto/syntax" "^0.4.3" 422 tslib "^2.8.1" 423 424 - "@atproto/lex-schema@^0.0.11": 425 - version "0.0.11" 426 - resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.11.tgz#7ff8a1e94971cb750cacaa40e30f546a64db0acd" 427 - integrity sha512-1vLUPQIMeawKP6ehSx2RiqaJDkiseFTXyUk3C4PaoFCktaH8FgVgPVKgUSSy02m1pxVMKnsOBV5psKeN53HG+Q== 428 dependencies: 429 - "@atproto/lex-data" "^0.0.10" 430 "@atproto/syntax" "^0.4.3" 431 tslib "^2.8.1" 432 ··· 441 multiformats "^9.9.0" 442 zod "^3.23.8" 443 444 - "@atproto/oauth-provider-api@0.3.7", "@atproto/oauth-provider-api@^0.3.7": 445 version "0.3.7" 446 resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.3.7.tgz#7b911256536a72dbba6f38081a200836a3ab50b8" 447 integrity sha512-7yU9vuQFt/hy4NzlDtn+LuhIGvVKkhgWAkCmopnseMPBw6oGPT90uOsTxMkVGtHuKVvBSz7hOXoELXpnZq3gDQ== ··· 449 "@atproto/jwk" "0.6.0" 450 "@atproto/oauth-types" "0.6.2" 451 452 - "@atproto/oauth-provider-frontend@^0.2.8": 453 - version "0.2.8" 454 - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.2.8.tgz#97f6dc33257b0f846229839dd74f606528f49846" 455 - integrity sha512-wHypQrsbwE6LUlyDADfiJfOH5pDAHWm4l/v1dwkQhRndOai7L+knsbdLAOZVXuz6bRs7oV2Frb3mSS03OS4Gdw== 456 optionalDependencies: 457 "@atproto/oauth-provider-api" "0.3.7" 458 459 - "@atproto/oauth-provider-ui@^0.4.2": 460 - version "0.4.2" 461 - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.4.2.tgz#4edd41c71fd1bf5aaa1df219fb8a20c0b8d90738" 462 - integrity sha512-j3Afu23JYy68GY8L4t/cTEZz1PnrvxLrjiG/nPhXPURZbCnqZ1puIU+HRVCBJfqKyCwaWthXgUjwaL+SFlPGKA== 463 optionalDependencies: 464 "@atproto/oauth-provider-api" "0.3.7" 465 466 - "@atproto/oauth-provider@^0.15.7": 467 - version "0.15.7" 468 - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.7.tgz#fc5bd420984dac60f726949d2473bbdb3ddff991" 469 - integrity sha512-rsPez44TJGVC5vFdwSTfSOLIIG5O1JF8NXLe1BjjsUbzRuflN6TyM6HyfBJVsnOVF2bOq6W6xPcnBFUJHCSyUQ== 470 dependencies: 471 "@atproto-labs/fetch" "^0.2.3" 472 "@atproto-labs/fetch-node" "^0.2.0" 473 "@atproto-labs/pipe" "^0.1.1" 474 "@atproto-labs/simple-store" "^0.3.0" 475 "@atproto-labs/simple-store-memory" "^0.1.4" 476 - "@atproto/common" "^0.5.10" 477 "@atproto/did" "^0.3.0" 478 "@atproto/jwk" "^0.6.0" 479 "@atproto/jwk-jose" "^0.1.11" 480 - "@atproto/lex-document" "^0.0.12" 481 - "@atproto/lex-resolver" "^0.0.13" 482 - "@atproto/oauth-provider-api" "^0.3.7" 483 - "@atproto/oauth-provider-frontend" "^0.2.8" 484 - "@atproto/oauth-provider-ui" "^0.4.2" 485 "@atproto/oauth-scopes" "^0.3.1" 486 - "@atproto/oauth-types" "^0.6.2" 487 "@atproto/syntax" "^0.4.3" 488 "@hapi/accept" "^6.0.3" 489 "@hapi/address" "^5.1.1" ··· 505 "@atproto/did" "^0.3.0" 506 "@atproto/syntax" "^0.4.3" 507 508 - "@atproto/oauth-types@0.6.2", "@atproto/oauth-types@^0.6.2": 509 version "0.6.2" 510 resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.2.tgz#d829fae63421dcea7ac703e84460175d2f8d9299" 511 integrity sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg== ··· 514 "@atproto/jwk" "0.6.0" 515 zod "^3.23.8" 516 517 - "@atproto/ozone@^0.1.162": 518 - version "0.1.162" 519 - resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.162.tgz#2c1775c1c45fb03b96087937c7675f7f1825c429" 520 - integrity sha512-UeSbHJ4qHIrWcRelifPm4BFh8PFN7DG9tpKuXSqFjZFG3hO2ZDyBzxV6e9zh7Qg+m2tibdst4Hrk4vyT/7wO6Q== 521 dependencies: 522 - "@atproto/api" "^0.18.18" 523 - "@atproto/common" "^0.5.9" 524 "@atproto/crypto" "^0.4.5" 525 - "@atproto/identity" "^0.4.10" 526 "@atproto/lexicon" "^0.6.1" 527 "@atproto/syntax" "^0.4.3" 528 "@atproto/ws-client" "^0.0.4" 529 "@atproto/xrpc" "^0.7.7" 530 - "@atproto/xrpc-server" "^0.10.10" 531 "@did-plc/lib" "^0.0.1" 532 compression "^1.7.4" 533 cors "^2.8.5" ··· 545 undici "^6.14.1" 546 ws "^8.12.0" 547 548 - "@atproto/pds@^0.4.207": 549 - version "0.4.207" 550 - resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.207.tgz#20ff3afcf1541b96f016a6843d69d3731b562f19" 551 - integrity sha512-LtzgqeD4aB3O1efzDbhdEopdujB4GG/klLT4N/3v35+0Jpylrkht5Ux2yJGKAqJkzFCfpBdRhNMWpJqS2ssu8A== 552 dependencies: 553 "@atproto-labs/fetch-node" "^0.2.0" 554 "@atproto-labs/simple-store" "^0.3.0" 555 "@atproto-labs/simple-store-memory" "^0.1.4" 556 "@atproto-labs/simple-store-redis" "^0.0.1" 557 "@atproto-labs/xrpc-utils" "^0.0.24" 558 - "@atproto/api" "^0.18.19" 559 "@atproto/aws" "^0.2.31" 560 - "@atproto/common" "^0.5.10" 561 "@atproto/crypto" "^0.4.5" 562 - "@atproto/identity" "^0.4.10" 563 - "@atproto/lex-cbor" "^0.0.10" 564 - "@atproto/lex-data" "^0.0.10" 565 "@atproto/lexicon" "^0.6.1" 566 - "@atproto/oauth-provider" "^0.15.7" 567 "@atproto/oauth-scopes" "^0.3.1" 568 "@atproto/repo" "^0.8.12" 569 "@atproto/syntax" "^0.4.3" 570 "@atproto/xrpc" "^0.7.7" 571 - "@atproto/xrpc-server" "^0.10.11" 572 "@did-plc/lib" "^0.0.4" 573 "@hapi/address" "^5.1.1" 574 better-sqlite3 "^10.0.0" ··· 642 "@atproto/common" "^0.5.3" 643 ws "^8.12.0" 644 645 - "@atproto/xrpc-server@^0.10.0", "@atproto/xrpc-server@^0.10.10", "@atproto/xrpc-server@^0.10.3": 646 version "0.10.10" 647 resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.10.tgz#69d1a3ce8278a6b49286a5df33ebc204d42129ed" 648 integrity sha512-USDjOZGiletqzuWHC3Q2fk30hJDk4uYt6KPgvnZidShCouTf3hzwJZ8d2eOKOofSjGXW+GC0QYp7fYJFn6lZ2Q== ··· 661 ws "^8.12.0" 662 zod "^3.23.8" 663 664 - "@atproto/xrpc-server@^0.10.11": 665 - version "0.10.11" 666 - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.11.tgz#cd6f880ebb6a913db2925a23aa0ef0b05f5b02fd" 667 - integrity sha512-7XR+n1G4j1PO33slr2Agl+lmTXbEQzk5iaCJmrcsfTC/0BbCHqSSbm+WHduz3EH4dfFioZsnDo1UesCF0EQEtg== 668 dependencies: 669 - "@atproto/common" "^0.5.10" 670 "@atproto/crypto" "^0.4.5" 671 - "@atproto/lex-cbor" "^0.0.10" 672 - "@atproto/lex-data" "^0.0.10" 673 "@atproto/lexicon" "^0.6.1" 674 "@atproto/ws-client" "^0.0.4" 675 "@atproto/xrpc" "^0.7.7"
··· 82 "@atproto/xrpc" "^0.7.6" 83 "@atproto/xrpc-server" "^0.10.0" 84 85 + "@atproto/api@^0.18.20": 86 + version "0.18.20" 87 + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.20.tgz#3fdbb7b7fae90bd59101970c2b56cc31e8cf417d" 88 + integrity sha512-BZYZkh2VJIFCXEnc/vzKwAwWjAQQTgbNJ8FBxpBK+z+KYh99O0uPCsRYKoCQsRrnkgrhzdU9+g2G+7zanTIGbw== 89 dependencies: 90 + "@atproto/common-web" "^0.4.15" 91 "@atproto/lexicon" "^0.6.1" 92 "@atproto/syntax" "^0.4.3" 93 "@atproto/xrpc" "^0.7.7" ··· 96 tlds "^1.234.0" 97 zod "^3.23.8" 98 99 + "@atproto/api@^0.18.21": 100 + version "0.18.21" 101 + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.21.tgz#19dbea422de22e7f5253f1c5475e3588c3a9d4a0" 102 + integrity sha512-s35MIJerGT/pKe2xJtKKswqlIr/ola2r2iURBKBL0Mk1OKe6jP4YvTMh1N2d2PEANFzNNTbKoDaLfJPo2Uvc/w== 103 dependencies: 104 + "@atproto/common-web" "^0.4.16" 105 "@atproto/lexicon" "^0.6.1" 106 "@atproto/syntax" "^0.4.3" 107 "@atproto/xrpc" "^0.7.7" ··· 128 multiformats "^9.9.0" 129 uint8arrays "3.0.0" 130 131 + "@atproto/bsky@^0.0.215": 132 + version "0.0.215" 133 + resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.215.tgz#df566ccc7bc77d27dd2f9506c96739352b395cef" 134 + integrity sha512-1X9G4L8boh9naCtXzorFYm8VskE1xLOJslthR16Hd4uH+ryOEVxN0BLk16OP86qy00hU6VNvyGkDqBthUyy62w== 135 dependencies: 136 "@atproto-labs/fetch-node" "^0.2.0" 137 "@atproto-labs/xrpc-utils" "^0.0.24" 138 + "@atproto/api" "^0.18.21" 139 + "@atproto/common" "^0.5.11" 140 "@atproto/crypto" "^0.4.5" 141 "@atproto/did" "^0.3.0" 142 + "@atproto/identity" "^0.4.11" 143 "@atproto/lexicon" "^0.6.1" 144 "@atproto/repo" "^0.8.12" 145 "@atproto/sync" "^0.1.39" 146 "@atproto/syntax" "^0.4.3" 147 + "@atproto/xrpc-server" "^0.10.12" 148 "@bufbuild/protobuf" "^1.5.0" 149 "@connectrpc/connect" "^1.1.4" 150 "@connectrpc/connect-express" "^1.1.4" ··· 194 pino-http "^8.2.1" 195 typed-emitter "^2.1.0" 196 197 + "@atproto/common-web@^0.4.13", "@atproto/common-web@^0.4.4", "@atproto/common-web@^0.4.7": 198 version "0.4.14" 199 resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.14.tgz#77545d454add08d735af00ffc29dd6abf1ab77b3" 200 integrity sha512-rMU8Q+kpyPpirUS9OqT7aOD1hxKa+diem3vc7BA0lOkj0tU6wcAxegxmbPZ8JaOsR7SSYhP/jCt/5wbT4qqkuQ== ··· 214 "@atproto/syntax" "^0.4.3" 215 zod "^3.23.8" 216 217 + "@atproto/common-web@^0.4.16": 218 + version "0.4.16" 219 + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.16.tgz#1bee6073ee7a102ea6c47a512a8f6992c683012e" 220 + integrity sha512-Ufvaff5JgxUyUyTAG0/3o7ltpy3lnZ1DvLjyAnvAf+hHfiK7OMQg+8byr+orN+KP9MtIQaRTsCgYPX+PxMKUoA== 221 + dependencies: 222 + "@atproto/lex-data" "^0.0.11" 223 + "@atproto/lex-json" "^0.0.11" 224 + "@atproto/syntax" "^0.4.3" 225 + zod "^3.23.8" 226 + 227 "@atproto/common@0.1.0": 228 version "0.1.0" 229 resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" ··· 256 multiformats "^9.9.0" 257 pino "^8.21.0" 258 259 + "@atproto/common@^0.5.11": 260 + version "0.5.11" 261 + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.11.tgz#88499978d3b29c451e7ad9a71f9d2f89446e89f6" 262 + integrity sha512-WRlT4s+wv80WdQuzkQub9D5vTD82O8dH2p91u4b+x3O17q5IQbmA3Lj+1NICINNSy2voqloqAWdqXEkRfdlAPw== 263 dependencies: 264 + "@atproto/common-web" "^0.4.16" 265 + "@atproto/lex-cbor" "^0.0.11" 266 + "@atproto/lex-data" "^0.0.11" 267 iso-datestring-validator "^2.2.2" 268 multiformats "^9.9.0" 269 pino "^8.21.0" ··· 288 "@noble/hashes" "^1.6.1" 289 uint8arrays "3.0.0" 290 291 + "@atproto/dev-env@^0.3.209": 292 + version "0.3.209" 293 + resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.209.tgz#0df234a7feab7628ad376773de0e8bd612fb7f2a" 294 + integrity sha512-i4IVqoyCAqULNeye9xpo5YVfrNjFQjGwAJZ4QAA26L9LuFlXmudnAX17KOCMvmJiPgtRe2ofIy6h+ZJ6qm52tQ== 295 dependencies: 296 + "@atproto/api" "^0.18.21" 297 + "@atproto/bsky" "^0.0.215" 298 "@atproto/bsync" "^0.0.23" 299 + "@atproto/common-web" "^0.4.16" 300 "@atproto/crypto" "^0.4.5" 301 + "@atproto/identity" "^0.4.11" 302 "@atproto/lexicon" "^0.6.1" 303 + "@atproto/ozone" "^0.1.163" 304 + "@atproto/pds" "^0.4.209" 305 "@atproto/sync" "^0.1.39" 306 "@atproto/syntax" "^0.4.3" 307 + "@atproto/xrpc-server" "^0.10.12" 308 "@did-plc/lib" "^0.0.1" 309 "@did-plc/server" "^0.0.1" 310 dotenv "^16.0.3" ··· 329 "@atproto/common-web" "^0.4.4" 330 "@atproto/crypto" "^0.4.4" 331 332 + "@atproto/identity@^0.4.11": 333 + version "0.4.11" 334 + resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.11.tgz#9622496499919cb2c05d21ab1ba45132dd1e3b52" 335 + integrity sha512-/xXPPojR0PRD0pqoEPmgKMeclUCrkaMKfFGFkssAmXSuT39aCtiibxNqvhp+S2jOdeM6rKrs2QgH91OCGvwPcg== 336 + dependencies: 337 + "@atproto/common-web" "^0.4.16" 338 + "@atproto/crypto" "^0.4.5" 339 + 340 "@atproto/jwk-jose@^0.1.11": 341 version "0.1.11" 342 resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz#ef64bce940a66e267fc3cf0db8df4dbd062bb28a" ··· 361 "@atproto/lex-data" "0.0.9" 362 tslib "^2.8.1" 363 364 + "@atproto/lex-cbor@^0.0.11": 365 + version "0.0.11" 366 + resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.11.tgz#7a464a2032eb61a1dfc254bae1e2759c8279d6a6" 367 + integrity sha512-A7ETtPsEsJ/VuPJOFw4bPNTKxHvFN1JbTQ2NjLuisd3ry7fVxgMpo/qGXsUQsAh/I/uziGbhpNqdS6GnI2p/Wg== 368 dependencies: 369 + "@atproto/lex-data" "^0.0.11" 370 tslib "^2.8.1" 371 372 + "@atproto/lex-client@^0.0.12": 373 + version "0.0.12" 374 + resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.12.tgz#87be8075746e32dafc3d23f282e53daadb50ea66" 375 + integrity sha512-ef4jQQ7SOtBsXr+Gf1UHuBfCiAGYZxO5PCCXl3eT4ObO83SROtIf7pyO06jBQI/IZChSVsXqXsgakR0aru6lYQ== 376 dependencies: 377 + "@atproto/lex-data" "^0.0.11" 378 + "@atproto/lex-json" "^0.0.11" 379 + "@atproto/lex-schema" "^0.0.12" 380 tslib "^2.8.1" 381 382 "@atproto/lex-data@0.0.9": ··· 399 uint8arrays "3.0.0" 400 unicode-segmenter "^0.14.0" 401 402 + "@atproto/lex-data@^0.0.11": 403 + version "0.0.11" 404 + resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.11.tgz#de98894a80ff907f9cd6c3faa561d3a16e21dd8d" 405 + integrity sha512-4+KTtHdqwlhiTKA7D4SACea4jprsNpCQsNALW09wsZ6IHhCDGO5tr1cmV+QnLYe3G3mu1E1yXHXbPUHrUUDT/A== 406 dependencies: 407 + multiformats "^9.9.0" 408 + tslib "^2.8.1" 409 + uint8arrays "3.0.0" 410 + unicode-segmenter "^0.14.0" 411 + 412 + "@atproto/lex-document@^0.0.13": 413 + version "0.0.13" 414 + resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.13.tgz#fcf794addf6cb328c78d4393e91ec0050a43c623" 415 + integrity sha512-LWsBsKIbyuG7jFObTtnCFQNYHxWWVpVVspqv6UtnS/QsaCyCMg1GIz5vlgi8QBnmGvaPiQxIzGt6mERpTvEXpg== 416 + dependencies: 417 + "@atproto/lex-schema" "^0.0.12" 418 core-js "^3" 419 tslib "^2.8.1" 420 ··· 434 "@atproto/lex-data" "^0.0.10" 435 tslib "^2.8.1" 436 437 + "@atproto/lex-json@^0.0.11": 438 + version "0.0.11" 439 + resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.11.tgz#56be96a4d2366113da3911020cedfb0fb97fbb4f" 440 + integrity sha512-2IExAoQ4KsR5fyPa1JjIvtR316PvdgRH/l3BVGLBd3cSxM3m5MftIv1B6qZ9HjNiK60SgkWp0mi9574bTNDhBQ== 441 + dependencies: 442 + "@atproto/lex-data" "^0.0.11" 443 + tslib "^2.8.1" 444 + 445 + "@atproto/lex-resolver@^0.0.14": 446 + version "0.0.14" 447 + resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.14.tgz#8bc32e49c231bc29ca0d1ea927b33d7a21447de3" 448 + integrity sha512-jBkYRmMKap2OM1zm0VDvs7Heuf3pGjw9xJEHQx1ohkMmM5f+cHPS40RQ8x8SNE+Vl9gMuOrgmgKyPDIuYSIBTw== 449 dependencies: 450 "@atproto-labs/did-resolver" "^0.2.6" 451 "@atproto/crypto" "^0.4.5" 452 + "@atproto/lex-client" "^0.0.12" 453 + "@atproto/lex-data" "^0.0.11" 454 + "@atproto/lex-document" "^0.0.13" 455 + "@atproto/lex-schema" "^0.0.12" 456 "@atproto/repo" "^0.8.12" 457 "@atproto/syntax" "^0.4.3" 458 tslib "^2.8.1" 459 460 + "@atproto/lex-schema@^0.0.12": 461 + version "0.0.12" 462 + resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.12.tgz#3519b1e4040503e1e6e5a47df1012660f4c38d8c" 463 + integrity sha512-l1RNYmqNwIEjgMEjC9i2o6FLsUFdpAc610xQYK/CRxN31cRzY0lAJ2GFbp2GZ4rRAD3FGYCXid6gZ42KsieUcw== 464 dependencies: 465 + "@atproto/lex-data" "^0.0.11" 466 "@atproto/syntax" "^0.4.3" 467 tslib "^2.8.1" 468 ··· 477 multiformats "^9.9.0" 478 zod "^3.23.8" 479 480 + "@atproto/oauth-provider-api@0.3.7": 481 version "0.3.7" 482 resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.3.7.tgz#7b911256536a72dbba6f38081a200836a3ab50b8" 483 integrity sha512-7yU9vuQFt/hy4NzlDtn+LuhIGvVKkhgWAkCmopnseMPBw6oGPT90uOsTxMkVGtHuKVvBSz7hOXoELXpnZq3gDQ== ··· 485 "@atproto/jwk" "0.6.0" 486 "@atproto/oauth-types" "0.6.2" 487 488 + "@atproto/oauth-provider-frontend@0.2.9": 489 + version "0.2.9" 490 + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.2.9.tgz#68486c0d9710a1325a02a303a6b22c733239b219" 491 + integrity sha512-m1NhqC7ydEpF5ELV13CxkgCawyMX8wbAXls/4af3Tsz3IOkdM6zPgQeYK7Zj+7Nywjml4mZTkJ8rZiT6O9+5sw== 492 optionalDependencies: 493 "@atproto/oauth-provider-api" "0.3.7" 494 495 + "@atproto/oauth-provider-ui@0.4.3": 496 + version "0.4.3" 497 + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.4.3.tgz#c80b076c2d2a7cc45e45866796f09b959bcf1c03" 498 + integrity sha512-BLNZmtOwoHu2qk/Oi6dUR8TcXQaJre6wgW8YjkW5bKf+Vftn3PGzh8bKgr1fQZDYweZ6AF148imA2OzVSAaLHQ== 499 optionalDependencies: 500 "@atproto/oauth-provider-api" "0.3.7" 501 502 + "@atproto/oauth-provider@^0.15.9": 503 + version "0.15.9" 504 + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.9.tgz#3ec21380711e34f4303a0782ecfffffb8080f144" 505 + integrity sha512-nuyOVw5piS/iEk7xKEtdulRbcFZ7HBB0gPXL62z5IpxmluEvLem/VLqsntj2OK/mtP6xhnIfFHP5pb5+PnLuYQ== 506 dependencies: 507 "@atproto-labs/fetch" "^0.2.3" 508 "@atproto-labs/fetch-node" "^0.2.0" 509 "@atproto-labs/pipe" "^0.1.1" 510 "@atproto-labs/simple-store" "^0.3.0" 511 "@atproto-labs/simple-store-memory" "^0.1.4" 512 + "@atproto/common" "^0.5.11" 513 "@atproto/did" "^0.3.0" 514 "@atproto/jwk" "^0.6.0" 515 "@atproto/jwk-jose" "^0.1.11" 516 + "@atproto/lex-document" "^0.0.13" 517 + "@atproto/lex-resolver" "^0.0.14" 518 + "@atproto/oauth-provider-api" "0.3.7" 519 + "@atproto/oauth-provider-frontend" "0.2.9" 520 + "@atproto/oauth-provider-ui" "0.4.3" 521 "@atproto/oauth-scopes" "^0.3.1" 522 + "@atproto/oauth-types" "^0.6.3" 523 "@atproto/syntax" "^0.4.3" 524 "@hapi/accept" "^6.0.3" 525 "@hapi/address" "^5.1.1" ··· 541 "@atproto/did" "^0.3.0" 542 "@atproto/syntax" "^0.4.3" 543 544 + "@atproto/oauth-types@0.6.2": 545 version "0.6.2" 546 resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.2.tgz#d829fae63421dcea7ac703e84460175d2f8d9299" 547 integrity sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg== ··· 550 "@atproto/jwk" "0.6.0" 551 zod "^3.23.8" 552 553 + "@atproto/oauth-types@^0.6.3": 554 + version "0.6.3" 555 + resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.3.tgz#4fc996d0af61874830079d1b1bddc7c0774ad6b2" 556 + integrity sha512-jdKuoPknJuh/WjI+mYk7agSbx9mNVMbS6Dr3k1z2YMY2oRiCQjxYBuo4MLKATbxj05nMQaZRWlHRUazoAu5Cng== 557 dependencies: 558 + "@atproto/did" "^0.3.0" 559 + "@atproto/jwk" "^0.6.0" 560 + zod "^3.23.8" 561 + 562 + "@atproto/ozone@^0.1.163": 563 + version "0.1.163" 564 + resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.163.tgz#7935f9180b6c7f3bd05043d42e14b6055c268596" 565 + integrity sha512-Rz5p0Bf9K3KVo6VuPNe5KTiuJVM5eV7fXSIeWidC1WbDgfEEM8jJu2YRQHvDQq5r95SKKY0gTFYqU6vW0DKSJA== 566 + dependencies: 567 + "@atproto/api" "^0.18.20" 568 + "@atproto/common" "^0.5.11" 569 "@atproto/crypto" "^0.4.5" 570 + "@atproto/identity" "^0.4.11" 571 "@atproto/lexicon" "^0.6.1" 572 "@atproto/syntax" "^0.4.3" 573 "@atproto/ws-client" "^0.0.4" 574 "@atproto/xrpc" "^0.7.7" 575 + "@atproto/xrpc-server" "^0.10.12" 576 "@did-plc/lib" "^0.0.1" 577 compression "^1.7.4" 578 cors "^2.8.5" ··· 590 undici "^6.14.1" 591 ws "^8.12.0" 592 593 + "@atproto/pds@^0.4.209": 594 + version "0.4.209" 595 + resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.209.tgz#eb12f325c55692a6487c0620a7754b13f0914fe1" 596 + integrity sha512-jW9dnrrqj65u6FZoj/idIez+idGgYBG4KGVB75i7pqrRkE1a91Bj5MXX60JfIQ0VY9YpfBthSnJG9Pzz5OzA/Q== 597 dependencies: 598 "@atproto-labs/fetch-node" "^0.2.0" 599 "@atproto-labs/simple-store" "^0.3.0" 600 "@atproto-labs/simple-store-memory" "^0.1.4" 601 "@atproto-labs/simple-store-redis" "^0.0.1" 602 "@atproto-labs/xrpc-utils" "^0.0.24" 603 + "@atproto/api" "^0.18.21" 604 "@atproto/aws" "^0.2.31" 605 + "@atproto/common" "^0.5.11" 606 "@atproto/crypto" "^0.4.5" 607 + "@atproto/identity" "^0.4.11" 608 + "@atproto/lex-cbor" "^0.0.11" 609 + "@atproto/lex-data" "^0.0.11" 610 "@atproto/lexicon" "^0.6.1" 611 + "@atproto/oauth-provider" "^0.15.9" 612 "@atproto/oauth-scopes" "^0.3.1" 613 "@atproto/repo" "^0.8.12" 614 "@atproto/syntax" "^0.4.3" 615 "@atproto/xrpc" "^0.7.7" 616 + "@atproto/xrpc-server" "^0.10.12" 617 "@did-plc/lib" "^0.0.4" 618 "@hapi/address" "^5.1.1" 619 better-sqlite3 "^10.0.0" ··· 687 "@atproto/common" "^0.5.3" 688 ws "^8.12.0" 689 690 + "@atproto/xrpc-server@^0.10.0", "@atproto/xrpc-server@^0.10.3": 691 version "0.10.10" 692 resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.10.tgz#69d1a3ce8278a6b49286a5df33ebc204d42129ed" 693 integrity sha512-USDjOZGiletqzuWHC3Q2fk30hJDk4uYt6KPgvnZidShCouTf3hzwJZ8d2eOKOofSjGXW+GC0QYp7fYJFn6lZ2Q== ··· 706 ws "^8.12.0" 707 zod "^3.23.8" 708 709 + "@atproto/xrpc-server@^0.10.12": 710 + version "0.10.12" 711 + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.12.tgz#6b88c9af5d301302e5e177b5f982a3494e734d78" 712 + integrity sha512-HuciFHxvQfWtvq+dmH3vK9uXii49vqkJgZFLcHums1xMvLvNBY5bgtUBdpQdsdEuq8B4DTuanZRs+z3zGariyg== 713 dependencies: 714 + "@atproto/common" "^0.5.11" 715 "@atproto/crypto" "^0.4.5" 716 + "@atproto/lex-cbor" "^0.0.11" 717 + "@atproto/lex-data" "^0.0.11" 718 "@atproto/lexicon" "^0.6.1" 719 "@atproto/ws-client" "^0.0.4" 720 "@atproto/xrpc" "^0.7.7"