Merge remote-tracking branch 'origin/main' into 3p-moderators

* origin/main: (33 commits)
  Improved server selector during account creation and signin (#2840)
  remove line height from text input for composer on ios (#2844)
  Add follow button to highlighted post (#2828)
  Cleaning up old codes pt_BR (#2809)
  Fix wrong translations in zh-CN localization  (#2812)
  Adjust Japanese translation (#2814)
  Update ja messages.po (#2821)
  change follow to follow all when !== 20 (#2831)
  ios adult content link fix 🤦 (#2845)
  improves build.md completeness (#2835)
  increase stroke width for active hashtag icon (#2829)
  Update `blueskyweb.xyz` links to `bsky.social` (#2830)
  eas nightlies (#2826)
  Always show post dropdown button at the bottom of the post, add share button to highlighted post (#2646)
  Measure header layout reliably (#2817)
  Round line height (#2824)
  Design system tweaks (#2822)
  Fix layout calculations (#2816)
  Fix flashes and jumps when opening profile (#2815)
  Show text cursor on web bio (#2813)
  ...
This commit is contained in:
Eric Bailey
2024-02-12 15:43:33 -06:00
90 changed files with 1781 additions and 1707 deletions
@@ -0,0 +1,52 @@
name: Deploy Nightly Testflight Release
on:
schedule:
- cron: '0 5 * * *'
jobs:
build:
name: Deploy Nightly Testflight Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check for EXPO_TOKEN
run: |
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
exit 1
fi
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: yarn
- name: Setup EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: Install dependencies
run: yarn install
- name: Bump build number
run: yarn bump:ios
- name: EAS build and submit
run: eas build -p ios --profile production --auto-submit --non-interactive
- name: Commit
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Nightly iOS Build Bump
branch: main
commit_user_name: github-actions[bot]
commit_user_email: github-actions[bot]@users.noreply.github.com
+2 -2
View File
@@ -6,7 +6,7 @@ Get the app itself:
- **Web: [bsky.app](https://bsky.app)** - **Web: [bsky.app](https://bsky.app)**
- **iOS: [App Store](https://apps.apple.com/us/app/bluesky-social/id6444370199)** - **iOS: [App Store](https://apps.apple.com/us/app/bluesky-social/id6444370199)**
- **Android: [Play Store](https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&hl=en_US&gl=US)** - **Android: [Play Store](https://play.google.com/store/apps/details?id=xyz.blueskyweb.app)**
## Development Resources ## Development Resources
@@ -21,7 +21,7 @@ The Authenticated Transfer Protocol ("AT Protocol" or "atproto") is a decentrali
- [Overview and Guides](https://atproto.com/guides/overview) - [Overview and Guides](https://atproto.com/guides/overview)
- [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions - [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions
- [Protocol Specifications](https://atproto.com/specs/atp) - [Protocol Specifications](https://atproto.com/specs/atp)
- [Blogpost on self-authenticating data structures](https://blueskyweb.xyz/blog/3-6-2022-a-self-authenticating-social-protocol) - [Blogpost on self-authenticating data structures](https://bsky.social/about/blog/3-6-2022-a-self-authenticating-social-protocol)
The Bluesky Social application encompasses a set of schemas and APIs built in the overall AT Protocol framework. The namespace for these "Lexicons" is `app.bsky.*`. The Bluesky Social application encompasses a set of schemas and APIs built in the overall AT Protocol framework. The namespace for these "Lexicons" is `app.bsky.*`.
+3 -1
View File
@@ -19,11 +19,13 @@ module.exports = function () {
/** /**
* iOS build number. Must be incremented for each TestFlight version. * iOS build number. Must be incremented for each TestFlight version.
* WARNING: Always leave this variable on line 24! If it is moved, you need to update ./scripts/bumpIosBuildNumber.sh
*/ */
const IOS_BUILD_NUMBER = '2' const IOS_BUILD_NUMBER = '3'
/** /**
* Android build number. Must be incremented for each release. * Android build number. Must be incremented for each release.
* WARNING: Always leave this variable on line 30! If it is moved, you need to update ./scripts/bumpAndroidBuildNumber.sh
*/ */
const ANDROID_VERSION_CODE = 61 const ANDROID_VERSION_CODE = 61
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12.707 3.293a1 1 0 0 0-1.414 0l-4.5 4.5a1 1 0 0 0 1.414 1.414L11 6.414v8.836a1 1 0 1 0 2 0V6.414l2.793 2.793a1 1 0 1 0 1.414-1.414l-4.5-4.5ZM5 12.75a1 1 0 1 0-2 0V20a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-7.25a1 1 0 1 0-2 0V19H5v-6.25Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 372 B

+4 -4
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover">
@@ -217,9 +217,9 @@
{%- block body_all %} {%- block body_all %}
<div id="root"></div> <div id="root"></div>
<noscript> <noscript>
<h1>JavaScript Required</h1> <h1 lang="en">JavaScript Required</h1>
<p>This is a heavily interactive web application, and JavaScript is required. Simple HTML interfaces are possible, but that is not what this is. <p lang="en">This is a heavily interactive web application, and JavaScript is required. Simple HTML interfaces are possible, but that is not what this is.
<p>Learn more about Bluesky at <a href="https://blueskyweb.xyz">blueskyweb.xyz</a> and <a href="https://atproto.com">atproto.com</a>. <p lang="en">Learn more about Bluesky at <a href="https://bsky.social">bsky.social</a> and <a href="https://atproto.com">atproto.com</a>.
{% block noscript_extra %}{% endblock %} {% block noscript_extra %}{% endblock %}
</noscript> </noscript>
{% endblock -%} {% endblock -%}
+11
View File
@@ -26,11 +26,22 @@
- Start the dev servers - Start the dev servers
- `git clone git@github.com:bluesky-social/atproto.git` - `git clone git@github.com:bluesky-social/atproto.git`
- `cd atproto` - `cd atproto`
- `brew install pnpm`
- `brew install jq`
- `pnpm i` - `pnpm i`
- `pnpm build` - `pnpm build`
- Start the docker daemon (on MacOS this entails starting the Docker Desktop app)
- Launch a Postgres database on port 5432
- `cd packages/dev-env && pnpm start` - `cd packages/dev-env && pnpm start`
- Run the dev app - Run the dev app
- iOS: `yarn ios` - iOS: `yarn ios`
- Xcode must be installed for this to run.
- A simulator must be preconfigured in Xcode settings.
- if no iOS versions are available, install the iOS runtime at `Xcode > Settings > Platforms`.
- In addition, ensure Xcode Command Line Tools are installed using `xcode-select --install`.
- Pods must be installed:
- From the project directory root: `cd ios && pod install`.
- Expo will require you to configure Xcode Signing. Follow the linked instructions. Error messages in Xcode related to the signing process can be safely ignored when installing on the iOS Simulator; Expo merely requires the profile to exist in order to install the app on the Simulator.
- Android: `yarn android` - Android: `yarn android`
- Web: `yarn web` - Web: `yarn web`
- If you are cloning or forking this repo as an open-source developer, please check the tips below as well - If you are cloning or forking this repo as an open-source developer, please check the tips below as well
+5 -1
View File
@@ -34,6 +34,10 @@
} }
}, },
"submit": { "submit": {
"production": {} "production": {
"ios": {
"ascAppId": "6444370199"
}
}
} }
} }
+4 -1
View File
@@ -36,7 +36,10 @@
"intl:check": "yarn intl:extract && git diff-index -G'(^[^\\*# /])|(^#\\w)|(^\\s+[^\\*#/])' HEAD || (echo '\n⚠️ i18n detected un-extracted translations\n' && exit 1)", "intl:check": "yarn intl:extract && git diff-index -G'(^[^\\*# /])|(^#\\w)|(^\\s+[^\\*#/])' HEAD || (echo '\n⚠️ i18n detected un-extracted translations\n' && exit 1)",
"intl:extract": "lingui extract", "intl:extract": "lingui extract",
"intl:compile": "lingui compile", "intl:compile": "lingui compile",
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android" "nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android",
"bump": "./scripts/bumpIosBuildNumber.sh && ./scripts/bumpAndroidBuildNumber.sh",
"bump:ios": "./scripts/bumpIosBuildNumber.sh",
"bump:android": "./scripts/bumpAndroidBuildNumber.sh"
}, },
"dependencies": { "dependencies": {
"@atproto/api": "^0.9.5", "@atproto/api": "^0.9.5",
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
# The number here should always be the line number the iOS build variable is on
line=$(sed "30q;d" ./app.config.js)
currentBuildNumber=$(echo "$line" | grep -oE '[0-9]+([.][0-9]+)?')
newBuildNumber=$((currentBuildNumber+1))
newBuildVariable="const ANDROID_VERSION_CODE = '$newBuildNumber'"
sed -i.bak "30s/.*/ $newBuildVariable/" ./app.config.js
rm -rf ./app.config.js.bak
echo "Android build number bumped to $newBuildNumber"
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
# The number here should always be the line number the iOS build variable is on
line=$(sed "24q;d" ./app.config.js)
currentBuildNumber=$(echo "$line" | grep -oE '[0-9]+([.][0-9]+)?')
newBuildNumber=$((currentBuildNumber+1))
newBuildVariable="const IOS_BUILD_NUMBER = '$newBuildNumber'"
sed -i.bak "24s/.*/ $newBuildVariable/" ./app.config.js
rm -rf ./app.config.js.bak
echo "iOS build number bumped to $newBuildNumber"
+14 -11
View File
@@ -32,6 +32,7 @@ import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
import I18nProvider from './locale/i18nProvider' import I18nProvider from './locale/i18nProvider'
import { import {
Provider as SessionProvider, Provider as SessionProvider,
@@ -72,17 +73,19 @@ function InnerApp() {
// Resets the entire tree below when it changes: // Resets the entire tree below when it changes:
key={currentAccount?.did}> key={currentAccount?.did}>
<LoggedOutViewProvider> <LoggedOutViewProvider>
<UnreadNotifsProvider> <SelectedFeedProvider>
<ThemeProvider theme={theme}> <UnreadNotifsProvider>
{/* All components should be within this provider */} <ThemeProvider theme={theme}>
<RootSiblingParent> {/* All components should be within this provider */}
<GestureHandlerRootView style={s.h100pct}> <RootSiblingParent>
<TestCtrls /> <GestureHandlerRootView style={s.h100pct}>
<Shell /> <TestCtrls />
</GestureHandlerRootView> <Shell />
</RootSiblingParent> </GestureHandlerRootView>
</ThemeProvider> </RootSiblingParent>
</UnreadNotifsProvider> </ThemeProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider> </LoggedOutViewProvider>
</React.Fragment> </React.Fragment>
</Splash> </Splash>
+14 -11
View File
@@ -22,6 +22,7 @@ import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
import I18nProvider from './locale/i18nProvider' import I18nProvider from './locale/i18nProvider'
import { import {
Provider as SessionProvider, Provider as SessionProvider,
@@ -52,17 +53,19 @@ function InnerApp() {
// Resets the entire tree below when it changes: // Resets the entire tree below when it changes:
key={currentAccount?.did}> key={currentAccount?.did}>
<LoggedOutViewProvider> <LoggedOutViewProvider>
<UnreadNotifsProvider> <SelectedFeedProvider>
<ThemeProvider theme={theme}> <UnreadNotifsProvider>
{/* All components should be within this provider */} <ThemeProvider theme={theme}>
<RootSiblingParent> {/* All components should be within this provider */}
<SafeAreaProvider> <RootSiblingParent>
<Shell /> <SafeAreaProvider>
</SafeAreaProvider> <Shell />
</RootSiblingParent> </SafeAreaProvider>
<ToastContainer /> </RootSiblingParent>
</ThemeProvider> <ToastContainer />
</UnreadNotifsProvider> </ThemeProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider> </LoggedOutViewProvider>
</React.Fragment> </React.Fragment>
</Alf> </Alf>
-1
View File
@@ -146,7 +146,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => ProfileScreen} getComponent={() => ProfileScreen}
options={({route}) => ({ options={({route}) => ({
title: bskyTitle(`@${route.params.name}`, unreadCountLabel), title: bskyTitle(`@${route.params.name}`, unreadCountLabel),
animation: 'none',
})} })}
/> />
<Stack.Screen <Stack.Screen
+162 -40
View File
@@ -73,19 +73,19 @@ export const darkPalette: Palette = {
white: tokens.color.gray_0, white: tokens.color.gray_0,
black: tokens.color.trueBlack, black: tokens.color.trueBlack,
contrast_25: tokens.color.gray_975, contrast_25: tokens.color.gray_1000,
contrast_50: tokens.color.gray_950, contrast_50: tokens.color.gray_975,
contrast_100: tokens.color.gray_900, contrast_100: tokens.color.gray_950,
contrast_200: tokens.color.gray_800, contrast_200: tokens.color.gray_900,
contrast_300: tokens.color.gray_700, contrast_300: tokens.color.gray_800,
contrast_400: tokens.color.gray_600, contrast_400: tokens.color.gray_700,
contrast_500: tokens.color.gray_500, contrast_500: tokens.color.gray_600,
contrast_600: tokens.color.gray_400, contrast_600: tokens.color.gray_500,
contrast_700: tokens.color.gray_300, contrast_700: tokens.color.gray_400,
contrast_800: tokens.color.gray_200, contrast_800: tokens.color.gray_300,
contrast_900: tokens.color.gray_100, contrast_900: tokens.color.gray_200,
contrast_950: tokens.color.gray_50, contrast_950: tokens.color.gray_100,
contrast_975: tokens.color.gray_25, contrast_975: tokens.color.gray_50,
primary_25: tokens.color.blue_25, primary_25: tokens.color.blue_25,
primary_50: tokens.color.blue_50, primary_50: tokens.color.blue_50,
@@ -133,6 +133,20 @@ export const darkPalette: Palette = {
export const dimPalette: Palette = { export const dimPalette: Palette = {
...darkPalette, ...darkPalette,
black: tokens.color.gray_1000, black: tokens.color.gray_1000,
contrast_25: tokens.color.gray_975,
contrast_50: tokens.color.gray_950,
contrast_100: tokens.color.gray_900,
contrast_200: tokens.color.gray_800,
contrast_300: tokens.color.gray_700,
contrast_400: tokens.color.gray_600,
contrast_500: tokens.color.gray_500,
contrast_600: tokens.color.gray_400,
contrast_700: tokens.color.gray_300,
contrast_800: tokens.color.gray_200,
contrast_900: tokens.color.gray_100,
contrast_950: tokens.color.gray_50,
contrast_975: tokens.color.gray_25,
} as const } as const
export const light = { export const light = {
@@ -142,17 +156,14 @@ export const light = {
text: { text: {
color: lightPalette.black, color: lightPalette.black,
}, },
text_contrast_700: { text_contrast_low: {
color: lightPalette.contrast_400,
},
text_contrast_medium: {
color: lightPalette.contrast_700, color: lightPalette.contrast_700,
}, },
text_contrast_600: { text_contrast_high: {
color: lightPalette.contrast_600, color: lightPalette.contrast_900,
},
text_contrast_500: {
color: lightPalette.contrast_500,
},
text_contrast_400: {
color: lightPalette.contrast_400,
}, },
text_inverted: { text_inverted: {
color: lightPalette.white, color: lightPalette.white,
@@ -175,11 +186,38 @@ export const light = {
bg_contrast_300: { bg_contrast_300: {
backgroundColor: lightPalette.contrast_300, backgroundColor: lightPalette.contrast_300,
}, },
border: { bg_contrast_400: {
backgroundColor: lightPalette.contrast_400,
},
bg_contrast_500: {
backgroundColor: lightPalette.contrast_500,
},
bg_contrast_600: {
backgroundColor: lightPalette.contrast_600,
},
bg_contrast_700: {
backgroundColor: lightPalette.contrast_700,
},
bg_contrast_800: {
backgroundColor: lightPalette.contrast_800,
},
bg_contrast_900: {
backgroundColor: lightPalette.contrast_900,
},
bg_contrast_950: {
backgroundColor: lightPalette.contrast_950,
},
bg_contrast_975: {
backgroundColor: lightPalette.contrast_975,
},
border_contrast_low: {
borderColor: lightPalette.contrast_100, borderColor: lightPalette.contrast_100,
}, },
border_contrast: { border_contrast_medium: {
borderColor: lightPalette.contrast_400, borderColor: lightPalette.contrast_200,
},
border_contrast_high: {
borderColor: lightPalette.contrast_300,
}, },
shadow_sm: { shadow_sm: {
...atoms.shadow_sm, ...atoms.shadow_sm,
@@ -203,17 +241,14 @@ export const dark: Theme = {
text: { text: {
color: darkPalette.white, color: darkPalette.white,
}, },
text_contrast_700: { text_contrast_low: {
color: darkPalette.contrast_400,
},
text_contrast_medium: {
color: darkPalette.contrast_700, color: darkPalette.contrast_700,
}, },
text_contrast_600: { text_contrast_high: {
color: darkPalette.contrast_600, color: darkPalette.contrast_900,
},
text_contrast_500: {
color: darkPalette.contrast_500,
},
text_contrast_400: {
color: darkPalette.contrast_400,
}, },
text_inverted: { text_inverted: {
color: darkPalette.black, color: darkPalette.black,
@@ -222,24 +257,51 @@ export const dark: Theme = {
backgroundColor: darkPalette.black, backgroundColor: darkPalette.black,
}, },
bg_contrast_25: { bg_contrast_25: {
backgroundColor: darkPalette.contrast_50, backgroundColor: darkPalette.contrast_25,
}, },
bg_contrast_50: { bg_contrast_50: {
backgroundColor: darkPalette.contrast_100, backgroundColor: darkPalette.contrast_50,
}, },
bg_contrast_100: { bg_contrast_100: {
backgroundColor: darkPalette.contrast_200, backgroundColor: darkPalette.contrast_100,
}, },
bg_contrast_200: { bg_contrast_200: {
backgroundColor: darkPalette.contrast_300, backgroundColor: darkPalette.contrast_200,
}, },
bg_contrast_300: { bg_contrast_300: {
backgroundColor: darkPalette.contrast_300,
},
bg_contrast_400: {
backgroundColor: darkPalette.contrast_400, backgroundColor: darkPalette.contrast_400,
}, },
border: { bg_contrast_500: {
backgroundColor: darkPalette.contrast_500,
},
bg_contrast_600: {
backgroundColor: darkPalette.contrast_600,
},
bg_contrast_700: {
backgroundColor: darkPalette.contrast_700,
},
bg_contrast_800: {
backgroundColor: darkPalette.contrast_800,
},
bg_contrast_900: {
backgroundColor: darkPalette.contrast_900,
},
bg_contrast_950: {
backgroundColor: darkPalette.contrast_950,
},
bg_contrast_975: {
backgroundColor: darkPalette.contrast_975,
},
border_contrast_low: {
borderColor: darkPalette.contrast_100, borderColor: darkPalette.contrast_100,
}, },
border_contrast: { border_contrast_medium: {
borderColor: darkPalette.contrast_200,
},
border_contrast_high: {
borderColor: darkPalette.contrast_300, borderColor: darkPalette.contrast_300,
}, },
shadow_sm: { shadow_sm: {
@@ -265,11 +327,71 @@ export const dim: Theme = {
name: 'dim', name: 'dim',
atoms: { atoms: {
...dark.atoms, ...dark.atoms,
text: {
color: dimPalette.white,
},
text_contrast_low: {
color: dimPalette.contrast_400,
},
text_contrast_medium: {
color: dimPalette.contrast_700,
},
text_contrast_high: {
color: dimPalette.contrast_900,
},
text_inverted: { text_inverted: {
color: dimPalette.black, color: dimPalette.black,
}, },
bg: { bg: {
backgroundColor: dimPalette.black, backgroundColor: dimPalette.black,
}, },
bg_contrast_25: {
backgroundColor: dimPalette.contrast_25,
},
bg_contrast_50: {
backgroundColor: dimPalette.contrast_50,
},
bg_contrast_100: {
backgroundColor: dimPalette.contrast_100,
},
bg_contrast_200: {
backgroundColor: dimPalette.contrast_200,
},
bg_contrast_300: {
backgroundColor: dimPalette.contrast_300,
},
bg_contrast_400: {
backgroundColor: dimPalette.contrast_400,
},
bg_contrast_500: {
backgroundColor: dimPalette.contrast_500,
},
bg_contrast_600: {
backgroundColor: dimPalette.contrast_600,
},
bg_contrast_700: {
backgroundColor: dimPalette.contrast_700,
},
bg_contrast_800: {
backgroundColor: dimPalette.contrast_800,
},
bg_contrast_900: {
backgroundColor: dimPalette.contrast_900,
},
bg_contrast_950: {
backgroundColor: dimPalette.contrast_950,
},
bg_contrast_975: {
backgroundColor: dimPalette.contrast_975,
},
border_contrast_low: {
borderColor: dimPalette.contrast_100,
},
border_contrast_medium: {
borderColor: dimPalette.contrast_200,
},
border_contrast_high: {
borderColor: dimPalette.contrast_300,
},
}, },
} }
+7 -7
View File
@@ -13,13 +13,13 @@ export const color = {
gray_300: `hsl(${BLUE_HUE}, 20%, 70%)`, gray_300: `hsl(${BLUE_HUE}, 20%, 70%)`,
gray_400: `hsl(${BLUE_HUE}, 20%, 60%)`, gray_400: `hsl(${BLUE_HUE}, 20%, 60%)`,
gray_500: `hsl(${BLUE_HUE}, 20%, 50%)`, gray_500: `hsl(${BLUE_HUE}, 20%, 50%)`,
gray_600: `hsl(${BLUE_HUE}, 20%, 42%)`, gray_600: `hsl(${BLUE_HUE}, 24%, 42%)`,
gray_700: `hsl(${BLUE_HUE}, 20%, 34%)`, gray_700: `hsl(${BLUE_HUE}, 24%, 34%)`,
gray_800: `hsl(${BLUE_HUE}, 20%, 26%)`, gray_800: `hsl(${BLUE_HUE}, 28%, 26%)`,
gray_900: `hsl(${BLUE_HUE}, 20%, 18%)`, gray_900: `hsl(${BLUE_HUE}, 28%, 18%)`,
gray_950: `hsl(${BLUE_HUE}, 20%, 10%)`, gray_950: `hsl(${BLUE_HUE}, 28%, 10%)`,
gray_975: `hsl(${BLUE_HUE}, 20%, 7%)`, gray_975: `hsl(${BLUE_HUE}, 28%, 7%)`,
gray_1000: `hsl(${BLUE_HUE}, 20%, 4%)`, gray_1000: `hsl(${BLUE_HUE}, 28%, 4%)`,
blue_25: `hsl(${BLUE_HUE}, 99%, 97%)`, blue_25: `hsl(${BLUE_HUE}, 99%, 97%)`,
blue_50: `hsl(${BLUE_HUE}, 99%, 95%)`, blue_50: `hsl(${BLUE_HUE}, 99%, 95%)`,
+1
View File
@@ -52,6 +52,7 @@ export type ButtonProps = React.PropsWithChildren<
Pick<PressableProps, 'disabled' | 'onPress' | 'testID'> & Pick<PressableProps, 'disabled' | 'onPress' | 'testID'> &
AccessibilityProps & AccessibilityProps &
VariantProps & { VariantProps & {
testID?: string
label: string label: string
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
} }
+15 -3
View File
@@ -35,6 +35,7 @@ export function Outer({
const sheet = React.useRef<BottomSheet>(null) const sheet = React.useRef<BottomSheet>(null)
const sheetOptions = nativeOptions?.sheet || {} const sheetOptions = nativeOptions?.sheet || {}
const hasSnapPoints = !!sheetOptions.snapPoints const hasSnapPoints = !!sheetOptions.snapPoints
const insets = useSafeAreaInsets()
const open = React.useCallback<DialogControlProps['open']>(({index} = {}) => { const open = React.useCallback<DialogControlProps['open']>(({index} = {}) => {
sheet.current?.snapToIndex(index || 0) sheet.current?.snapToIndex(index || 0)
@@ -42,8 +43,7 @@ export function Outer({
const close = React.useCallback(() => { const close = React.useCallback(() => {
sheet.current?.close() sheet.current?.close()
onClose?.() }, [])
}, [onClose])
useImperativeHandle( useImperativeHandle(
control.ref, control.ref,
@@ -54,6 +54,15 @@ export function Outer({
[open, close], [open, close],
) )
const onChange = React.useCallback(
(index: number) => {
if (index === -1) {
onClose?.()
}
},
[onClose],
)
const context = React.useMemo(() => ({close}), [close]) const context = React.useMemo(() => ({close}), [close])
return ( return (
@@ -64,6 +73,7 @@ export function Outer({
keyboardBehavior="interactive" keyboardBehavior="interactive"
android_keyboardInputMode="adjustResize" android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore" keyboardBlurBehavior="restore"
topInset={insets.top}
{...sheetOptions} {...sheetOptions}
ref={sheet} ref={sheet}
index={defaultOpen ? 0 : -1} index={defaultOpen ? 0 : -1}
@@ -78,7 +88,7 @@ export function Outer({
)} )}
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}} handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}} handleStyle={{display: 'none'}}
onClose={onClose}> onChange={onChange}>
<Context.Provider value={context}> <Context.Provider value={context}>
<View <View
style={[ style={[
@@ -122,6 +132,8 @@ export function ScrollableInner(props: DialogInnerProps) {
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
return ( return (
<BottomSheetScrollView <BottomSheetScrollView
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
style={[ style={[
a.flex_1, // main diff is this a.flex_1, // main diff is this
a.p_xl, a.p_xl,
+11 -3
View File
@@ -1,10 +1,18 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme, ViewStyleProp, flatten} from '#/alf'
import {ViewStyleProp} from '#/alf'
export function Divider({style}: ViewStyleProp) { export function Divider({style}: ViewStyleProp) {
const t = useTheme() const t = useTheme()
return <View style={[a.w_full, a.border_t, t.atoms.border, style]} /> return (
<View
style={[
a.w_full,
a.border_t,
t.atoms.border_contrast_low,
flatten(style),
]}
/>
)
} }
+7 -8
View File
@@ -4,7 +4,7 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useTheme, atoms as a} from '#/alf' import {useTheme, atoms as a} from '#/alf'
import {H4, P} from '#/components/Typography' import {Text} from '#/components/Typography'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
@@ -50,14 +50,11 @@ export function Outer({
} }
export function Title({children}: React.PropsWithChildren<{}>) { export function Title({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
const {titleId} = React.useContext(Context) const {titleId} = React.useContext(Context)
return ( return (
<H4 <Text nativeID={titleId} style={[a.text_2xl, a.font_bold, a.pb_sm]}>
nativeID={titleId}
style={[a.font_bold, t.atoms.text_contrast_700, a.pb_sm]}>
{children} {children}
</H4> </Text>
) )
} }
@@ -65,9 +62,11 @@ export function Description({children}: React.PropsWithChildren<{}>) {
const t = useTheme() const t = useTheme()
const {descriptionId} = React.useContext(Context) const {descriptionId} = React.useContext(Context)
return ( return (
<P nativeID={descriptionId} style={[t.atoms.text, a.pb_lg]}> <Text
nativeID={descriptionId}
style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high, a.pb_lg]}>
{children} {children}
</P> </Text>
) )
} }
+30 -126
View File
@@ -15,7 +15,7 @@ export function leading<
>(textSize: Size, leading: Leading) { >(textSize: Size, leading: Leading) {
const size = textSize?.fontSize || atoms.text_md.fontSize const size = textSize?.fontSize || atoms.text_md.fontSize
const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
return size * lineHeight return Math.round(size * lineHeight)
} }
/** /**
@@ -32,7 +32,7 @@ function normalizeTextStyles(styles: TextStyle[]) {
if (s?.lineHeight) { if (s?.lineHeight) {
if (s.lineHeight <= 2) { if (s.lineHeight <= 2) {
s.lineHeight = fontSize * s.lineHeight s.lineHeight = Math.round(fontSize * s.lineHeight)
} }
} else { } else {
s.lineHeight = fontSize s.lineHeight = fontSize
@@ -41,138 +41,42 @@ function normalizeTextStyles(styles: TextStyle[]) {
return s return s
} }
/**
* Our main text component. Use this most of the time.
*/
export function Text({style, ...rest}: TextProps) { export function Text({style, ...rest}: TextProps) {
const t = useTheme() const t = useTheme()
const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)]) const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)])
return <RNText style={s} {...rest} /> return <RNText style={s} {...rest} />
} }
export function H1({style, ...rest}: TextProps) { export function createHeadingElement({level}: {level: number}) {
const t = useTheme() return function HeadingElement({style, ...rest}: TextProps) {
const attr = const t = useTheme()
web({ const attr =
role: 'heading', web({
'aria-level': 1, role: 'heading',
}) || {} 'aria-level': level,
return ( }) || {}
<RNText return (
{...attr} <RNText
{...rest} {...attr}
style={normalizeTextStyles([ {...rest}
atoms.text_5xl, style={normalizeTextStyles([t.atoms.text, flatten(style)])}
atoms.font_bold, />
t.atoms.text, )
flatten(style), }
])}
/>
)
}
export function H2({style, ...rest}: TextProps) {
const t = useTheme()
const attr =
web({
role: 'heading',
'aria-level': 2,
}) || {}
return (
<RNText
{...attr}
{...rest}
style={normalizeTextStyles([
atoms.text_4xl,
atoms.font_bold,
t.atoms.text,
flatten(style),
])}
/>
)
}
export function H3({style, ...rest}: TextProps) {
const t = useTheme()
const attr =
web({
role: 'heading',
'aria-level': 3,
}) || {}
return (
<RNText
{...attr}
{...rest}
style={normalizeTextStyles([
atoms.text_3xl,
atoms.font_bold,
t.atoms.text,
flatten(style),
])}
/>
)
}
export function H4({style, ...rest}: TextProps) {
const t = useTheme()
const attr =
web({
role: 'heading',
'aria-level': 4,
}) || {}
return (
<RNText
{...attr}
{...rest}
style={normalizeTextStyles([
atoms.text_2xl,
atoms.font_bold,
t.atoms.text,
flatten(style),
])}
/>
)
}
export function H5({style, ...rest}: TextProps) {
const t = useTheme()
const attr =
web({
role: 'heading',
'aria-level': 5,
}) || {}
return (
<RNText
{...attr}
{...rest}
style={normalizeTextStyles([
atoms.text_xl,
atoms.font_bold,
t.atoms.text,
flatten(style),
])}
/>
)
}
export function H6({style, ...rest}: TextProps) {
const t = useTheme()
const attr =
web({
role: 'heading',
'aria-level': 6,
}) || {}
return (
<RNText
{...attr}
{...rest}
style={normalizeTextStyles([
atoms.text_lg,
atoms.font_bold,
t.atoms.text,
flatten(style),
])}
/>
)
} }
/*
* Use semantic components when it's beneficial to the user or to a web scraper
*/
export const H1 = createHeadingElement({level: 1})
export const H2 = createHeadingElement({level: 2})
export const H3 = createHeadingElement({level: 3})
export const H4 = createHeadingElement({level: 4})
export const H5 = createHeadingElement({level: 5})
export const H6 = createHeadingElement({level: 6})
export function P({style, ...rest}: TextProps) { export function P({style, ...rest}: TextProps) {
const t = useTheme() const t = useTheme()
const attr = const attr =
+8 -3
View File
@@ -241,10 +241,15 @@ export function createInput(Component: typeof TextInput) {
export const Input = createInput(TextInput) export const Input = createInput(TextInput)
export function Label({children}: React.PropsWithChildren<{}>) { export function Label({
nativeID,
children,
}: React.PropsWithChildren<{nativeID?: string}>) {
const t = useTheme() const t = useTheme()
return ( return (
<Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_600, a.mb_sm]}> <Text
nativeID={nativeID}
style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium, a.mb_sm]}>
{children} {children}
</Text> </Text>
) )
@@ -318,7 +323,7 @@ export function Suffix({
a.z_20, a.z_20,
a.pr_sm, a.pr_sm,
a.text_md, a.text_md,
t.atoms.text_contrast_400, t.atoms.text_contrast_medium,
{ {
pointerEvents: 'none', pointerEvents: 'none',
}, },
+3 -3
View File
@@ -347,7 +347,7 @@ export function Checkbox() {
a.align_center, a.align_center,
a.border, a.border,
a.rounded_xs, a.rounded_xs,
t.atoms.border_contrast, t.atoms.border_contrast_high,
{ {
height: 20, height: 20,
width: 20, width: 20,
@@ -393,7 +393,7 @@ export function Switch() {
a.border, a.border,
a.rounded_full, a.rounded_full,
t.atoms.bg, t.atoms.bg,
t.atoms.border_contrast, t.atoms.border_contrast_high,
{ {
height: 20, height: 20,
width: 30, width: 30,
@@ -445,7 +445,7 @@ export function Radio() {
a.align_center, a.align_center,
a.border, a.border,
a.rounded_full, a.rounded_full,
t.atoms.border_contrast, t.atoms.border_contrast_high,
{ {
height: 20, height: 20,
width: 20, width: 20,
+4 -4
View File
@@ -8,7 +8,7 @@ import * as Toggle from '#/components/forms/Toggle'
export type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> & export type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> &
AccessibilityProps & AccessibilityProps &
React.PropsWithChildren<{}> React.PropsWithChildren<{testID?: string}>
export type GroupProps = Omit<Toggle.GroupProps, 'style' | 'type'> & { export type GroupProps = Omit<Toggle.GroupProps, 'style' | 'type'> & {
multiple?: boolean multiple?: boolean
@@ -25,7 +25,7 @@ export function Group({children, multiple, ...props}: GroupProps) {
a.border, a.border,
a.rounded_sm, a.rounded_sm,
a.overflow_hidden, a.overflow_hidden,
t.atoms.border, t.atoms.border_contrast_low,
]}> ]}>
{children} {children}
</View> </View>
@@ -103,7 +103,7 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
}), }),
a.px_sm, a.px_sm,
t.atoms.bg, t.atoms.bg,
t.atoms.border, t.atoms.border_contrast_low,
baseStyles, baseStyles,
activeStyles, activeStyles,
(state.hovered || state.focused || state.pressed) && hoverStyles, (state.hovered || state.focused || state.pressed) && hoverStyles,
@@ -113,7 +113,7 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
style={[ style={[
a.text_center, a.text_center,
a.font_bold, a.font_bold,
t.atoms.text_contrast_500, t.atoms.text_contrast_medium,
textStyles, textStyles,
]}> ]}>
{children} {children}
+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const ArrowOutOfBox_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12.707 3.293a1 1 0 0 0-1.414 0l-4.5 4.5a1 1 0 0 0 1.414 1.414L11 6.414v8.836a1 1 0 1 0 2 0V6.414l2.793 2.793a1 1 0 1 0 1.414-1.414l-4.5-4.5ZM5 12.75a1 1 0 1 0-2 0V20a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-7.25a1 1 0 1 0-2 0V19H5v-6.25Z',
})
+1 -1
View File
@@ -219,7 +219,7 @@ export const COUNTRY_CODES: CountryCodeMap[] = [
{code2: 'SE', name: 'Sweden (+46)'}, {code2: 'SE', name: 'Sweden (+46)'},
{code2: 'CH', name: 'Switzerland (+41)'}, {code2: 'CH', name: 'Switzerland (+41)'},
{code2: 'SY', name: 'Syrian Arab Republic (+963)'}, {code2: 'SY', name: 'Syrian Arab Republic (+963)'},
{code2: 'TW', name: 'Taiwan, Province of China (+886)'}, {code2: 'TW', name: 'Taiwan (+886)'},
{code2: 'TJ', name: 'Tajikistan (+992)'}, {code2: 'TJ', name: 'Tajikistan (+992)'},
{code2: 'TZ', name: 'Tanzania, United Republic of (+255)'}, {code2: 'TZ', name: 'Tanzania, United Republic of (+255)'},
{code2: 'TH', name: 'Thailand (+66)'}, {code2: 'TH', name: 'Thailand (+66)'},
+1 -1
View File
@@ -236,7 +236,7 @@ export function lh(
height: number, height: number,
): TextStyle { ): TextStyle {
return { return {
lineHeight: (theme.typography[type].fontSize || 16) * height, lineHeight: Math.round((theme.typography[type].fontSize || 16) * height),
} }
} }
+1 -1
View File
@@ -2445,7 +2445,7 @@ msgstr "Hinweis: Bluesky ist ein offenes und öffentliches Netzwerk. Diese Einst
#: src/view/shell/Drawer.tsx:438 #: src/view/shell/Drawer.tsx:438
#: src/view/shell/Drawer.tsx:439 #: src/view/shell/Drawer.tsx:439
msgid "Notifications" msgid "Notifications"
msgstr "Benachrichtigungen" msgstr "Mitteilungen"
#: src/view/com/modals/SelfLabel.tsx:103 #: src/view/com/modals/SelfLabel.tsx:103
msgid "Nudity" msgid "Nudity"
+61 -61
View File
@@ -9,8 +9,8 @@ msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-01-30 19:00+0900\n" "PO-Revision-Date: 2024-01-30 19:00+0900\n"
"Last-Translator: dolciss\n" "Last-Translator: Hima-Zinn\n"
"Language-Team: Hima-Zinn, tkusano, dolciss, oboenikui, noritada\n" "Language-Team: Hima-Zinn, tkusano, dolciss, oboenikui, noritada, middlingphys\n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/view/com/modals/VerifyEmail.tsx:142 #: src/view/com/modals/VerifyEmail.tsx:142
@@ -242,7 +242,7 @@ msgstr "高度な設定"
#: src/view/com/auth/login/ForgotPasswordForm.tsx:217 #: src/view/com/auth/login/ForgotPasswordForm.tsx:217
#: src/view/com/modals/ChangePassword.tsx:168 #: src/view/com/modals/ChangePassword.tsx:168
msgid "Already have a code?" msgid "Already have a code?"
msgstr "" msgstr "コードをすでに持っていますか?"
#: src/view/com/auth/login/ChooseAccountForm.tsx:98 #: src/view/com/auth/login/ChooseAccountForm.tsx:98
msgid "Already signed in as @{0}" msgid "Already signed in as @{0}"
@@ -280,7 +280,7 @@ msgstr "および"
#: src/screens/Onboarding/index.tsx:32 #: src/screens/Onboarding/index.tsx:32
msgid "Animals" msgid "Animals"
msgstr "" msgstr "動物"
#: src/view/screens/LanguageSettings.tsx:95 #: src/view/screens/LanguageSettings.tsx:95
msgid "App Language" msgid "App Language"
@@ -334,7 +334,7 @@ msgstr "この判断に異議を申し立てる"
#: src/view/screens/Settings.tsx:460 #: src/view/screens/Settings.tsx:460
msgid "Appearance" msgid "Appearance"
msgstr "外観" msgstr "背景"
#: src/view/screens/AppPasswords.tsx:224 #: src/view/screens/AppPasswords.tsx:224
msgid "Are you sure you want to delete the app password \"{name}\"?" msgid "Are you sure you want to delete the app password \"{name}\"?"
@@ -358,7 +358,7 @@ msgstr "<0>{0}</0>で書かれた投稿ですか?"
#: src/screens/Onboarding/index.tsx:26 #: src/screens/Onboarding/index.tsx:26
msgid "Art" msgid "Art"
msgstr "" msgstr "アート"
#: src/view/com/modals/SelfLabel.tsx:123 #: src/view/com/modals/SelfLabel.tsx:123
msgid "Artistic or non-erotic nudity." msgid "Artistic or non-erotic nudity."
@@ -476,7 +476,7 @@ msgstr "Blueskyはパブリックです。"
#: src/view/com/modals/Waitlist.tsx:70 #: src/view/com/modals/Waitlist.tsx:70
msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon."
msgstr "Blueskyはより健全なコミュニティを構築するために招待状を使用します。招待状をお持ちでない場合、Waitlistにお申し込みいただくと招待状をお送りします。" msgstr "Blueskyはより健全なコミュニティを構築するために招待状を使用します。招待状をお持ちでない場合、Waitlistにお申し込みいただくと招待状をお送りします。"
#: src/view/screens/Moderation.tsx:226 #: src/view/screens/Moderation.tsx:226
msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private."
@@ -488,7 +488,7 @@ msgstr "Bluesky.Social"
#: src/screens/Onboarding/index.tsx:33 #: src/screens/Onboarding/index.tsx:33
msgid "Books" msgid "Books"
msgstr "" msgstr "書籍"
#: src/view/screens/Settings.tsx:841 #: src/view/screens/Settings.tsx:841
msgid "Build version {0} {1}" msgid "Build version {0} {1}"
@@ -616,11 +616,11 @@ msgstr "メールアドレスを変更"
#: src/view/screens/Settings.tsx:726 #: src/view/screens/Settings.tsx:726
msgid "Change password" msgid "Change password"
msgstr "" msgstr "パスワードを変更"
#: src/view/screens/Settings.tsx:735 #: src/view/screens/Settings.tsx:735
msgid "Change Password" msgid "Change Password"
msgstr "" msgstr "パスワードを変更"
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73
msgid "Change post language to {0}" msgid "Change post language to {0}"
@@ -628,7 +628,7 @@ msgstr "投稿の言語を{0}に変更します"
#: src/view/screens/Settings.tsx:727 #: src/view/screens/Settings.tsx:727
msgid "Change your Bluesky password" msgid "Change your Bluesky password"
msgstr "" msgstr "Blueskyのパスワードを変更"
#: src/view/com/modals/ChangeEmail.tsx:109 #: src/view/com/modals/ChangeEmail.tsx:109
msgid "Change Your Email" msgid "Change Your Email"
@@ -682,7 +682,7 @@ msgstr "メインのフィードを選択"
#: src/view/com/auth/create/Step1.tsx:163 #: src/view/com/auth/create/Step1.tsx:163
msgid "Choose your password" msgid "Choose your password"
msgstr "パスワードを選択" msgstr "パスワードを入力"
#: src/view/screens/Settings.tsx:816 #: src/view/screens/Settings.tsx:816
#: src/view/screens/Settings.tsx:817 #: src/view/screens/Settings.tsx:817
@@ -713,12 +713,12 @@ msgstr "こちらをクリック"
#: src/screens/Onboarding/index.tsx:35 #: src/screens/Onboarding/index.tsx:35
msgid "Climate" msgid "Climate"
msgstr "" msgstr "気象"
#: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:265
#: src/view/com/modals/ChangePassword.tsx:268 #: src/view/com/modals/ChangePassword.tsx:268
msgid "Close" msgid "Close"
msgstr "" msgstr "閉じる"
#: src/components/Dialog/index.web.tsx:78 #: src/components/Dialog/index.web.tsx:78
msgid "Close active dialog" msgid "Close active dialog"
@@ -766,16 +766,16 @@ msgstr "指定した通知のユーザーリストを折りたたむ"
#: src/screens/Onboarding/index.tsx:41 #: src/screens/Onboarding/index.tsx:41
msgid "Comedy" msgid "Comedy"
msgstr "" msgstr "コメディー"
#: src/screens/Onboarding/index.tsx:27 #: src/screens/Onboarding/index.tsx:27
msgid "Comics" msgid "Comics"
msgstr "" msgstr "漫画"
#: src/Navigation.tsx:228 #: src/Navigation.tsx:228
#: src/view/screens/CommunityGuidelines.tsx:32 #: src/view/screens/CommunityGuidelines.tsx:32
msgid "Community Guidelines" msgid "Community Guidelines"
msgstr "コミュニティガイドライン" msgstr "コミュニティガイドライン"
#: src/screens/Onboarding/StepFinished.tsx:148 #: src/screens/Onboarding/StepFinished.tsx:148
msgid "Complete onboarding and start using your account" msgid "Complete onboarding and start using your account"
@@ -898,7 +898,7 @@ msgstr "アカウントをフォローせずに次のステップへ進む"
#: src/screens/Onboarding/index.tsx:44 #: src/screens/Onboarding/index.tsx:44
msgid "Cooking" msgid "Cooking"
msgstr "" msgstr "料理"
#: src/view/com/modals/AddAppPasswords.tsx:195 #: src/view/com/modals/AddAppPasswords.tsx:195
#: src/view/com/modals/InviteCodes.tsx:182 #: src/view/com/modals/InviteCodes.tsx:182
@@ -997,7 +997,7 @@ msgstr "サムネイル付きのカードを作成します。そのカードは
#: src/screens/Onboarding/index.tsx:29 #: src/screens/Onboarding/index.tsx:29
msgid "Culture" msgid "Culture"
msgstr "" msgstr "文化"
#: src/view/com/modals/ChangeHandle.tsx:389 #: src/view/com/modals/ChangeHandle.tsx:389
#: src/view/com/modals/ServerInput.tsx:102 #: src/view/com/modals/ServerInput.tsx:102
@@ -1006,7 +1006,7 @@ msgstr "カスタムドメイン"
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106
msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." msgid "Custom feeds built by the community bring you new experiences and help you find the content you love."
msgstr "" msgstr "コミュニティーによって作成されたカスタムフィードは、あなたに新しい体験をもたらし、あなたが好きなコンテンツを見つけるのに役立ちます。"
#: src/view/screens/PreferencesExternalEmbeds.tsx:55 #: src/view/screens/PreferencesExternalEmbeds.tsx:55
msgid "Customize media from external sites." msgid "Customize media from external sites."
@@ -1027,7 +1027,7 @@ msgstr "ダークモード"
#: src/view/screens/Settings.tsx:492 #: src/view/screens/Settings.tsx:492
msgid "Dark Theme" msgid "Dark Theme"
msgstr "" msgstr "ダークテーマ"
#: src/Navigation.tsx:204 #: src/Navigation.tsx:204
#~ msgid "Debug" #~ msgid "Debug"
@@ -1065,7 +1065,7 @@ msgstr "マイアカウントを削除"
#: src/view/screens/Settings.tsx:755 #: src/view/screens/Settings.tsx:755
msgid "Delete My Account…" msgid "Delete My Account…"
msgstr "" msgstr "マイアカウントを削除…"
#: src/view/com/util/forms/PostDropdownBtn.tsx:228 #: src/view/com/util/forms/PostDropdownBtn.tsx:228
msgid "Delete post" msgid "Delete post"
@@ -1104,7 +1104,7 @@ msgstr "なにか言いたいことはあった?"
#: src/view/screens/Settings.tsx:498 #: src/view/screens/Settings.tsx:498
msgid "Dim" msgid "Dim"
msgstr "" msgstr "グレー"
#: src/view/com/composer/Composer.tsx:144 #: src/view/com/composer/Composer.tsx:144
msgid "Discard" msgid "Discard"
@@ -1210,7 +1210,7 @@ msgstr "例:返信として広告を繰り返し送ってくるユーザー。
#: src/view/com/modals/InviteCodes.tsx:96 #: src/view/com/modals/InviteCodes.tsx:96
msgid "Each code works once. You'll receive more invite codes periodically." msgid "Each code works once. You'll receive more invite codes periodically."
msgstr "それぞれのコードは一度ずつ動作します。定期的に招待コードをお送りします。" msgstr "それぞれのコードは一回限り有効です。定期的に追加の招待コードをお送りします。"
#: src/view/com/lists/ListMembers.tsx:149 #: src/view/com/lists/ListMembers.tsx:149
msgctxt "action" msgctxt "action"
@@ -1266,7 +1266,7 @@ msgstr "あなたのプロフィールの説明を編集します"
#: src/screens/Onboarding/index.tsx:34 #: src/screens/Onboarding/index.tsx:34
msgid "Education" msgid "Education"
msgstr "" msgstr "教育"
#: src/view/com/auth/create/Step1.tsx:143 #: src/view/com/auth/create/Step1.tsx:143
#: src/view/com/auth/create/Step2.tsx:194 #: src/view/com/auth/create/Step2.tsx:194
@@ -1342,7 +1342,7 @@ msgstr "確認コードを入力してください"
#: src/view/com/modals/ChangePassword.tsx:151 #: src/view/com/modals/ChangePassword.tsx:151
msgid "Enter the code you received to change your password." msgid "Enter the code you received to change your password."
msgstr "" msgstr "パスワードを変更するために受け取ったコードを入力してください。"
#: src/view/com/modals/ChangeHandle.tsx:371 #: src/view/com/modals/ChangeHandle.tsx:371
msgid "Enter the domain you want to use" msgid "Enter the domain you want to use"
@@ -1535,7 +1535,7 @@ msgstr "ディスカッションスレッドを微調整します。"
#: src/screens/Onboarding/index.tsx:38 #: src/screens/Onboarding/index.tsx:38
msgid "Fitness" msgid "Fitness"
msgstr "" msgstr "フィットネス"
#: src/screens/Onboarding/StepFinished.tsx:131 #: src/screens/Onboarding/StepFinished.tsx:131
msgid "Flexible" msgid "Flexible"
@@ -1623,7 +1623,7 @@ msgstr "あなたをフォロー"
#: src/screens/Onboarding/index.tsx:43 #: src/screens/Onboarding/index.tsx:43
msgid "Food" msgid "Food"
msgstr "" msgstr "食べ物"
#: src/view/com/modals/DeleteAccount.tsx:111 #: src/view/com/modals/DeleteAccount.tsx:111
msgid "For security reasons, we'll need to send a confirmation code to your email address." msgid "For security reasons, we'll need to send a confirmation code to your email address."
@@ -1658,7 +1658,7 @@ msgstr "ギャラリー"
#: src/view/com/modals/VerifyEmail.tsx:189 #: src/view/com/modals/VerifyEmail.tsx:189
#: src/view/com/modals/VerifyEmail.tsx:191 #: src/view/com/modals/VerifyEmail.tsx:191
msgid "Get Started" msgid "Get Started"
msgstr "はじめに" msgstr "開始"
#: src/view/com/auth/LoggedOut.tsx:81 #: src/view/com/auth/LoggedOut.tsx:81
#: src/view/com/auth/LoggedOut.tsx:82 #: src/view/com/auth/LoggedOut.tsx:82
@@ -1825,11 +1825,11 @@ msgstr "ALTテキストが長い場合、ALTテキストの展開状態を切り
#: src/view/com/modals/SelfLabel.tsx:127 #: src/view/com/modals/SelfLabel.tsx:127
msgid "If none are selected, suitable for all ages." msgid "If none are selected, suitable for all ages."
msgstr "選択されていない場合は、すべての年齢に適しています。" msgstr "何も選択しない場合は、全年齢対象です。"
#: src/view/com/modals/ChangePassword.tsx:146 #: src/view/com/modals/ChangePassword.tsx:146
msgid "If you want to change your password, we will send you a code to verify that this is your account." msgid "If you want to change your password, we will send you a code to verify that this is your account."
msgstr "" msgstr "パスワードを変更する場合は、あなたのアカウントであることを確認するためのコードをお送りします。"
#: src/view/com/util/images/Gallery.tsx:38 #: src/view/com/util/images/Gallery.tsx:38
msgid "Image" msgid "Image"
@@ -1969,7 +1969,7 @@ msgstr "Waitlistに参加"
#: src/screens/Onboarding/index.tsx:24 #: src/screens/Onboarding/index.tsx:24
msgid "Journalism" msgid "Journalism"
msgstr "" msgstr "報道"
#: src/view/com/composer/select-language/SelectLangBtn.tsx:104 #: src/view/com/composer/select-language/SelectLangBtn.tsx:104
msgid "Language selection" msgid "Language selection"
@@ -2272,7 +2272,7 @@ msgstr "モデレーションの設定"
#: src/view/com/modals/ModerationDetails.tsx:35 #: src/view/com/modals/ModerationDetails.tsx:35
msgid "Moderator has chosen to set a general warning on the content." msgid "Moderator has chosen to set a general warning on the content."
msgstr "モデレーターはその投稿に一般的な警告設定を選択しました。" msgstr "モデレーターによりコンテンツに一般的な警告設定されました。"
#: src/view/shell/desktop/Feeds.tsx:53 #: src/view/shell/desktop/Feeds.tsx:53
msgid "More feeds" msgid "More feeds"
@@ -2364,7 +2364,7 @@ msgstr "名前は必須です"
#: src/screens/Onboarding/index.tsx:25 #: src/screens/Onboarding/index.tsx:25
msgid "Nature" msgid "Nature"
msgstr "" msgstr "自然"
#: src/view/com/auth/login/ForgotPasswordForm.tsx:186 #: src/view/com/auth/login/ForgotPasswordForm.tsx:186
#: src/view/com/auth/login/ForgotPasswordForm.tsx:215 #: src/view/com/auth/login/ForgotPasswordForm.tsx:215
@@ -2411,7 +2411,7 @@ msgstr "新しいパスワード"
#: src/view/com/modals/ChangePassword.tsx:215 #: src/view/com/modals/ChangePassword.tsx:215
msgid "New Password" msgid "New Password"
msgstr "" msgstr "新しいパスワード"
#: src/view/com/feeds/FeedPage.tsx:201 #: src/view/com/feeds/FeedPage.tsx:201
msgctxt "action" msgctxt "action"
@@ -2446,7 +2446,7 @@ msgstr "新しい順に返信を表示"
#: src/screens/Onboarding/index.tsx:23 #: src/screens/Onboarding/index.tsx:23
msgid "News" msgid "News"
msgstr "" msgstr "ニュース"
#: src/view/com/auth/create/CreateAccount.tsx:161 #: src/view/com/auth/create/CreateAccount.tsx:161
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178 #: src/view/com/auth/login/ForgotPasswordForm.tsx:178
@@ -2525,7 +2525,7 @@ msgstr "見つかりません"
#: src/view/com/modals/VerifyEmail.tsx:246 #: src/view/com/modals/VerifyEmail.tsx:246
#: src/view/com/modals/VerifyEmail.tsx:252 #: src/view/com/modals/VerifyEmail.tsx:252
msgid "Not right now" msgid "Not right now"
msgstr "今すぐにではない" msgstr "今はない"
#: src/view/screens/Moderation.tsx:227 #: src/view/screens/Moderation.tsx:227
#~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users." #~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users."
@@ -2768,7 +2768,7 @@ msgstr "カメラへのアクセスが拒否されました。システムの設
#: src/screens/Onboarding/index.tsx:31 #: src/screens/Onboarding/index.tsx:31
msgid "Pets" msgid "Pets"
msgstr "" msgstr "ペット"
#: src/view/com/auth/create/Step2.tsx:183 #: src/view/com/auth/create/Step2.tsx:183
msgid "Phone number" msgid "Phone number"
@@ -2776,7 +2776,7 @@ msgstr "電話番号"
#: src/view/com/modals/SelfLabel.tsx:121 #: src/view/com/modals/SelfLabel.tsx:121
msgid "Pictures meant for adults." msgid "Pictures meant for adults."
msgstr "成人向けの写真です。" msgstr "成人向けの画像です。"
#: src/view/screens/ProfileFeed.tsx:353 #: src/view/screens/ProfileFeed.tsx:353
#: src/view/screens/ProfileList.tsx:580 #: src/view/screens/ProfileList.tsx:580
@@ -2860,7 +2860,7 @@ msgstr "リンクカードがロードされるまでお待ちください"
#: src/screens/Onboarding/index.tsx:37 #: src/screens/Onboarding/index.tsx:37
msgid "Politics" msgid "Politics"
msgstr "" msgstr "政治"
#: src/view/com/modals/SelfLabel.tsx:111 #: src/view/com/modals/SelfLabel.tsx:111
msgid "Porn" msgid "Porn"
@@ -3187,7 +3187,7 @@ msgstr "コードをリクエスト"
#: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:239
#: src/view/com/modals/ChangePassword.tsx:241 #: src/view/com/modals/ChangePassword.tsx:241
msgid "Request Code" msgid "Request Code"
msgstr "" msgstr "コードをリクエスト"
#: src/view/screens/Settings.tsx:450 #: src/view/screens/Settings.tsx:450
msgid "Require alt text before posting" msgid "Require alt text before posting"
@@ -3204,7 +3204,7 @@ msgstr "コードをリセット"
#: src/view/com/modals/ChangePassword.tsx:190 #: src/view/com/modals/ChangePassword.tsx:190
msgid "Reset Code" msgid "Reset Code"
msgstr "" msgstr "コードをリセット"
#: src/view/screens/Settings.tsx:806 #: src/view/screens/Settings.tsx:806
msgid "Reset onboarding" msgid "Reset onboarding"
@@ -3312,7 +3312,7 @@ msgstr "{handle}へのハンドルの変更を保存"
#: src/screens/Onboarding/index.tsx:36 #: src/screens/Onboarding/index.tsx:36
msgid "Science" msgid "Science"
msgstr "" msgstr "科学"
#: src/view/screens/ProfileList.tsx:854 #: src/view/screens/ProfileList.tsx:854
msgid "Scroll to top" msgid "Scroll to top"
@@ -3470,7 +3470,7 @@ msgstr "年齢を設定"
#: src/view/screens/Settings.tsx:482 #: src/view/screens/Settings.tsx:482
msgid "Set color theme to dark" msgid "Set color theme to dark"
msgstr "カラーテーマをダークに設定します" msgstr "カラーテーマを暗いものに設定します"
#: src/view/screens/Settings.tsx:475 #: src/view/screens/Settings.tsx:475
msgid "Set color theme to light" msgid "Set color theme to light"
@@ -3478,15 +3478,15 @@ msgstr "カラーテーマをライトに設定します"
#: src/view/screens/Settings.tsx:469 #: src/view/screens/Settings.tsx:469
msgid "Set color theme to system setting" msgid "Set color theme to system setting"
msgstr "システム設定のカラーテーマを使用するように設定します" msgstr "デバイスで設定したカラーテーマを使用するように設定します"
#: src/view/screens/Settings.tsx:508 #: src/view/screens/Settings.tsx:508
msgid "Set dark theme to the dark theme" msgid "Set dark theme to the dark theme"
msgstr "" msgstr "ダークテーマをダークに設定します"
#: src/view/screens/Settings.tsx:501 #: src/view/screens/Settings.tsx:501
msgid "Set dark theme to the dim theme" msgid "Set dark theme to the dim theme"
msgstr "" msgstr "ダークテーマを薄暗いものに設定します"
#: src/view/com/auth/login/SetNewPasswordForm.tsx:104 #: src/view/com/auth/login/SetNewPasswordForm.tsx:104
msgid "Set new password" msgid "Set new password"
@@ -3717,7 +3717,7 @@ msgstr "サインアップ"
#: src/view/shell/NavSignupCard.tsx:42 #: src/view/shell/NavSignupCard.tsx:42
msgid "Sign up or sign in to join the conversation" msgid "Sign up or sign in to join the conversation"
msgstr "登録またはログインして会話に参加" msgstr "サインアップまたはサインインして会話に参加"
#: src/view/com/util/moderation/ScreenHider.tsx:76 #: src/view/com/util/moderation/ScreenHider.tsx:76
msgid "Sign-in Required" msgid "Sign-in Required"
@@ -3751,7 +3751,7 @@ msgstr "SMS認証"
#: src/screens/Onboarding/index.tsx:40 #: src/screens/Onboarding/index.tsx:40
msgid "Software Dev" msgid "Software Dev"
msgstr "" msgstr "ソフトウェア開発"
#: src/view/com/modals/ProfilePreview.tsx:62 #: src/view/com/modals/ProfilePreview.tsx:62
msgid "Something went wrong and we're not sure what." msgid "Something went wrong and we're not sure what."
@@ -3775,7 +3775,7 @@ msgstr "次の方法で同じ投稿への返信を並び替えます。"
#: src/screens/Onboarding/index.tsx:30 #: src/screens/Onboarding/index.tsx:30
msgid "Sports" msgid "Sports"
msgstr "" msgstr "スポーツ"
#: src/view/com/modals/crop-image/CropImage.web.tsx:122 #: src/view/com/modals/crop-image/CropImage.web.tsx:122
msgid "Square" msgid "Square"
@@ -3837,7 +3837,7 @@ msgstr "あなたへのおすすめ"
#: src/view/com/modals/SelfLabel.tsx:95 #: src/view/com/modals/SelfLabel.tsx:95
msgid "Suggestive" msgid "Suggestive"
msgstr "提案" msgstr "きわどい"
#: src/Navigation.tsx:213 #: src/Navigation.tsx:213
#: src/view/screens/Support.tsx:30 #: src/view/screens/Support.tsx:30
@@ -3881,7 +3881,7 @@ msgstr "タップして全体を表示"
#: src/screens/Onboarding/index.tsx:39 #: src/screens/Onboarding/index.tsx:39
msgid "Tech" msgid "Tech"
msgstr "" msgstr "テクノロジー"
#: src/view/shell/desktop/RightNav.tsx:93 #: src/view/shell/desktop/RightNav.tsx:93
msgid "Terms" msgid "Terms"
@@ -3905,7 +3905,7 @@ msgstr "このアカウントは、ブロック解除後にあなたとやり取
#: src/view/screens/CommunityGuidelines.tsx:36 #: src/view/screens/CommunityGuidelines.tsx:36
msgid "The Community Guidelines have been moved to <0/>" msgid "The Community Guidelines have been moved to <0/>"
msgstr "コミュニティガイドラインは<0/>に移動しました" msgstr "コミュニティガイドラインは<0/>に移動しました"
#: src/view/screens/CopyrightPolicy.tsx:33 #: src/view/screens/CopyrightPolicy.tsx:33
msgid "The Copyright Policy has been moved to <0/>" msgid "The Copyright Policy has been moved to <0/>"
@@ -4105,7 +4105,7 @@ msgstr "このユーザーは、あなたがブロックした<0/>リストに
#: src/view/com/modals/ModerationDetails.tsx:74 #: src/view/com/modals/ModerationDetails.tsx:74
msgid "This user is included in the <0/> list which you have muted." msgid "This user is included in the <0/> list which you have muted."
msgstr "" msgstr "このユーザーは、あなたがミュートした<0/>リストに含まれています。"
#: src/view/com/modals/ModerationDetails.tsx:74 #: src/view/com/modals/ModerationDetails.tsx:74
#~ msgid "This user is included the <0/> list which you have muted." #~ msgid "This user is included the <0/> list which you have muted."
@@ -4113,7 +4113,7 @@ msgstr ""
#: src/view/com/modals/SelfLabel.tsx:137 #: src/view/com/modals/SelfLabel.tsx:137
msgid "This warning is only available for posts with media attached." msgid "This warning is only available for posts with media attached."
msgstr "この警告は、メディアが接続されている投稿にのみ使用できます。" msgstr "この警告は、メディアが添付されている投稿にのみ使用できます。"
#: src/view/com/util/forms/PostDropdownBtn.tsx:192 #: src/view/com/util/forms/PostDropdownBtn.tsx:192
msgid "This will hide this post from your feeds." msgid "This will hide this post from your feeds."
@@ -4368,7 +4368,7 @@ msgstr "メールアドレスを確認"
#: src/screens/Onboarding/index.tsx:42 #: src/screens/Onboarding/index.tsx:42
msgid "Video Games" msgid "Video Games"
msgstr "" msgstr "ビデオゲーム"
#: src/view/com/profile/ProfileHeader.tsx:701 #: src/view/com/profile/ProfileHeader.tsx:701
msgid "View {0}'s avatar" msgid "View {0}'s avatar"
@@ -4504,7 +4504,7 @@ msgstr "返信を書く"
#: src/screens/Onboarding/index.tsx:28 #: src/screens/Onboarding/index.tsx:28
msgid "Writers" msgid "Writers"
msgstr "" msgstr "ライター"
#: src/view/com/auth/create/Step2.tsx:263 #: src/view/com/auth/create/Step2.tsx:263
msgid "XXXXXX" msgid "XXXXXX"
@@ -4568,7 +4568,7 @@ msgstr "保存されたフィードがありません。"
#: src/view/com/post-thread/PostThread.tsx:406 #: src/view/com/post-thread/PostThread.tsx:406
msgid "You have blocked the author or you have been blocked by the author." msgid "You have blocked the author or you have been blocked by the author."
msgstr "あなたが者をブロックしているか、または者によってあなたはブロックされています。" msgstr "あなたが投稿者をブロックしているか、または投稿者によってあなたはブロックされています。"
#: src/view/com/modals/ModerationDetails.tsx:56 #: src/view/com/modals/ModerationDetails.tsx:56
msgid "You have blocked this user. You cannot view their content." msgid "You have blocked this user. You cannot view their content."
@@ -4579,7 +4579,7 @@ msgstr "あなたはこのユーザーをブロックしているため、コン
#: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:87
#: src/view/com/modals/ChangePassword.tsx:121 #: src/view/com/modals/ChangePassword.tsx:121
msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." msgid "You have entered an invalid code. It should look like XXXXX-XXXXX."
msgstr "" msgstr "無効なコードが入力されました。それはXXXXX-XXXXXのようになっているはずです。"
#: src/view/com/modals/ModerationDetails.tsx:87 #: src/view/com/modals/ModerationDetails.tsx:87
msgid "You have muted this user." msgid "You have muted this user."
@@ -4676,7 +4676,7 @@ msgstr "メールアドレスが保存されました!すぐにご連絡いた
#: src/view/com/modals/ChangeEmail.tsx:125 #: src/view/com/modals/ChangeEmail.tsx:125
msgid "Your email has been updated but not verified. As a next step, please verify your new email." msgid "Your email has been updated but not verified. As a next step, please verify your new email."
msgstr "メールアドレスは更新されましたが、確認されていません。次のステップとして、新しいEメールを確認してください。" msgstr "メールアドレスは更新されましたが、確認されていません。次のステップとして、新しいメールアドレスを確認してください。"
#: src/view/com/modals/VerifyEmail.tsx:114 #: src/view/com/modals/VerifyEmail.tsx:114
msgid "Your email has not yet been verified. This is an important security step which we recommend." msgid "Your email has not yet been verified. This is an important security step which we recommend."
@@ -4706,7 +4706,7 @@ msgstr "アプリパスワードを使用してログインすると、招待コ
#: src/view/com/modals/ChangePassword.tsx:155 #: src/view/com/modals/ChangePassword.tsx:155
msgid "Your password has been changed successfully!" msgid "Your password has been changed successfully!"
msgstr "" msgstr "パスワードの変更が完了しました!"
#: src/view/com/composer/Composer.tsx:267 #: src/view/com/composer/Composer.tsx:267
msgid "Your post has been published" msgid "Your post has been published"
+20 -305
View File
@@ -8,17 +8,11 @@ msgstr ""
"Language: pt-BR\n" "Language: pt-BR\n"
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-02-07 00:25\n" "PO-Revision-Date: 2024-02-08 19:59\n"
"Last-Translator: maisondasilva\n" "Last-Translator: maisondasilva\n"
"Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum\n" "Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#~ msgid "- end of feed -"
#~ msgstr "- fim do feed -"
#~ msgid ". This warning is only available for posts with media attached."
#~ msgstr ". Este aviso só aparece em posts com imagens."
#: src/view/com/modals/VerifyEmail.tsx:142 #: src/view/com/modals/VerifyEmail.tsx:142
msgid "(no email)" msgid "(no email)"
msgstr "(sem email)" msgstr "(sem email)"
@@ -27,15 +21,6 @@ msgstr "(sem email)"
msgid "{0, plural, one {# invite code available} other {# invite codes available}}" msgid "{0, plural, one {# invite code available} other {# invite codes available}}"
msgstr "{0, plural, one {# convite disponível} other {# convites disponíveis}}" msgstr "{0, plural, one {# convite disponível} other {# convites disponíveis}}"
#: src/view/com/modals/CreateOrEditList.tsx:185
#: src/view/screens/Settings.tsx:294
#~ msgid "{0}"
#~ msgstr "{0}"
#: src/view/com/modals/CreateOrEditList.tsx:176
#~ msgid "{0} {purposeLabel} List"
#~ msgstr "Lista {0} {purposeLabel}"
#: src/view/com/profile/ProfileHeader.tsx:632 #: src/view/com/profile/ProfileHeader.tsx:632
msgid "{following} following" msgid "{following} following"
msgstr "{following} seguindo" msgstr "{following} seguindo"
@@ -54,18 +39,10 @@ msgstr "{invitesAvailable} convite disponível"
msgid "{invitesAvailable} invite codes available" msgid "{invitesAvailable} invite codes available"
msgstr "{invitesAvailable} convites disponíveis" msgstr "{invitesAvailable} convites disponíveis"
#: src/view/screens/Search/Search.tsx:87
#~ msgid "{message}"
#~ msgstr "{message}"
#: src/view/shell/Drawer.tsx:443 #: src/view/shell/Drawer.tsx:443
msgid "{numUnreadNotifications} unread" msgid "{numUnreadNotifications} unread"
msgstr "{numUnreadNotifications} não lidas" msgstr "{numUnreadNotifications} não lidas"
#: src/Navigation.tsx:147
#~ msgid "@{0}"
#~ msgstr "@{0}"
#: src/view/com/threadgate/WhoCanReply.tsx:158 #: src/view/com/threadgate/WhoCanReply.tsx:158
msgid "<0/> members" msgid "<0/> members"
msgstr "<0/> membros" msgstr "<0/> membros"
@@ -82,18 +59,6 @@ msgstr "<0>Escolha seus</0><2>Feeds</2><1>recomendados</1>"
msgid "<0>Follow some</0><1>Recommended</1><2>Users</2>" msgid "<0>Follow some</0><1>Recommended</1><2>Users</2>"
msgstr "<0>Siga alguns</0><2>Usuários</2><1>recomendados</1>" msgstr "<0>Siga alguns</0><2>Usuários</2><1>recomendados</1>"
#: src/view/com/modals/AddAppPasswords.tsx:132
#~ msgid "<0>Here is your app password.</0> Use this to sign into the other app along with your handle."
#~ msgstr "<0>Aqui está sua senha de aplicativo.</0> Use-a com seu usuário para logar em outro aplicativo."
#: src/view/screens/Moderation.tsx:212
#~ msgid "<0>Note: This setting may not be respected by third-party apps that display Bluesky content.</0>"
#~ msgstr "<0>Nota: Esta opção pode não ser respeitada por aplicativos de terceiro que mostram conteúdo do Bluesky.</0>"
#: src/view/screens/Moderation.tsx:212
#~ msgid "<0>Note: Your profile and posts will remain publicly available. Third-party apps that display Bluesky content may not respect this setting.</0>"
#~ msgstr "<0>Nota: Seu perfil e posts continuarão públicos. Aplicativos de terceiros que mostram conteúdo do Bluesky podem não respeitar esta opção."
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:21 #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:21
msgid "<0>Welcome to</0><1>Bluesky</1>" msgid "<0>Welcome to</0><1>Bluesky</1>"
msgstr "<0>Bem-vindo ao</0><1>Bluesky</1>" msgstr "<0>Bem-vindo ao</0><1>Bluesky</1>"
@@ -260,7 +225,7 @@ msgstr "Avançado"
#: src/view/com/auth/login/ForgotPasswordForm.tsx:217 #: src/view/com/auth/login/ForgotPasswordForm.tsx:217
#: src/view/com/modals/ChangePassword.tsx:168 #: src/view/com/modals/ChangePassword.tsx:168
msgid "Already have a code?" msgid "Already have a code?"
msgstr "" msgstr "Já tem um código?"
#: src/view/com/auth/login/ChooseAccountForm.tsx:98 #: src/view/com/auth/login/ChooseAccountForm.tsx:98
msgid "Already signed in as @{0}" msgid "Already signed in as @{0}"
@@ -338,10 +303,6 @@ msgstr "Contestar aviso de conteúdo"
msgid "Appeal Content Warning" msgid "Appeal Content Warning"
msgstr "Contestar aviso de conteúdo" msgstr "Contestar aviso de conteúdo"
#: src/view/com/modals/AppealLabel.tsx:65
#~ msgid "Appeal Decision"
#~ msgstr "Contestar Decisão"
#: src/view/com/util/moderation/LabelInfo.tsx:52 #: src/view/com/util/moderation/LabelInfo.tsx:52
msgid "Appeal this decision" msgid "Appeal this decision"
msgstr "Contestar esta decisão" msgstr "Contestar esta decisão"
@@ -354,10 +315,6 @@ msgstr "Contestar esta decisão."
msgid "Appearance" msgid "Appearance"
msgstr "Aparência" msgstr "Aparência"
#: src/view/screens/Moderation.tsx:206
#~ msgid "Apps that respect this setting, including the official Bluesky app and bsky.app website, won't show your content to logged out users."
#~ msgstr "Aplicativos que respeitam esta configuração, incluindo os aplicativos oficiais do Bluesky e o site, não mostrarão seu conteúdo para usuários deslogados."
#: src/view/screens/AppPasswords.tsx:224 #: src/view/screens/AppPasswords.tsx:224
msgid "Are you sure you want to delete the app password \"{name}\"?" msgid "Are you sure you want to delete the app password \"{name}\"?"
msgstr "Tem certeza de que deseja excluir a senha do aplicativo \"{name}\"?" msgstr "Tem certeza de que deseja excluir a senha do aplicativo \"{name}\"?"
@@ -386,10 +343,6 @@ msgstr "Arte"
msgid "Artistic or non-erotic nudity." msgid "Artistic or non-erotic nudity."
msgstr "Nudez artística ou não erótica." msgstr "Nudez artística ou não erótica."
#: src/view/screens/Moderation.tsx:189
#~ msgid "Ask apps to limit the visibility of my account"
#~ msgstr "Exigir visibilidade limitada da minha conta"
#: src/view/com/auth/create/CreateAccount.tsx:147 #: src/view/com/auth/create/CreateAccount.tsx:147
#: src/view/com/auth/login/ChooseAccountForm.tsx:151 #: src/view/com/auth/login/ChooseAccountForm.tsx:151
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170 #: src/view/com/auth/login/ForgotPasswordForm.tsx:170
@@ -589,10 +542,6 @@ msgstr "Cancelar"
msgid "Cancel account deletion" msgid "Cancel account deletion"
msgstr "Cancelar exclusão da conta" msgstr "Cancelar exclusão da conta"
#: src/view/com/modals/AltImage.tsx:123
#~ msgid "Cancel add image alt text"
#~ msgstr "Cancelar adição de texto alternativo da imagem"
#: src/view/com/modals/ChangeHandle.tsx:149 #: src/view/com/modals/ChangeHandle.tsx:149
msgid "Cancel change handle" msgid "Cancel change handle"
msgstr "Cancelar alteração de usuário" msgstr "Cancelar alteração de usuário"
@@ -623,10 +572,6 @@ msgctxt "action"
msgid "Change" msgid "Change"
msgstr "Alterar" msgstr "Alterar"
#: src/view/screens/Settings.tsx:306
#~ msgid "Change"
#~ msgstr "Alterar"
#: src/view/screens/Settings.tsx:690 #: src/view/screens/Settings.tsx:690
msgid "Change handle" msgid "Change handle"
msgstr "Alterar usuário" msgstr "Alterar usuário"
@@ -642,11 +587,11 @@ msgstr "Alterar meu email"
#: src/view/screens/Settings.tsx:726 #: src/view/screens/Settings.tsx:726
msgid "Change password" msgid "Change password"
msgstr "" msgstr "Alterar senha"
#: src/view/screens/Settings.tsx:735 #: src/view/screens/Settings.tsx:735
msgid "Change Password" msgid "Change Password"
msgstr "" msgstr "Alterar Senha"
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73
msgid "Change post language to {0}" msgid "Change post language to {0}"
@@ -654,7 +599,7 @@ msgstr "Trocar idioma do post para {0}"
#: src/view/screens/Settings.tsx:727 #: src/view/screens/Settings.tsx:727
msgid "Change your Bluesky password" msgid "Change your Bluesky password"
msgstr "" msgstr "Alterar sua senha do Bluesky"
#: src/view/com/modals/ChangeEmail.tsx:109 #: src/view/com/modals/ChangeEmail.tsx:109
msgid "Change Your Email" msgid "Change Your Email"
@@ -698,10 +643,6 @@ msgstr "Escolha os algoritmos que geram seus feeds customizados."
msgid "Choose the algorithms that power your experience with custom feeds." msgid "Choose the algorithms that power your experience with custom feeds."
msgstr "Escolha os algoritmos que fazem sentido para você com os feeds personalizados." msgstr "Escolha os algoritmos que fazem sentido para você com os feeds personalizados."
#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:65
#~ msgid "Choose your"
#~ msgstr "Escolha seu"
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103
#~ msgid "Choose your algorithmic feeds" #~ msgid "Choose your algorithmic feeds"
#~ msgstr "Escolha seus feeds algoritmicos" #~ msgstr "Escolha seus feeds algoritmicos"
@@ -748,7 +689,7 @@ msgstr "Clima e tempo"
#: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:265
#: src/view/com/modals/ChangePassword.tsx:268 #: src/view/com/modals/ChangePassword.tsx:268
msgid "Close" msgid "Close"
msgstr "" msgstr "Fechar"
#: src/components/Dialog/index.web.tsx:78 #: src/components/Dialog/index.web.tsx:78
msgid "Close active dialog" msgid "Close active dialog"
@@ -1036,7 +977,7 @@ msgstr "Domínio personalizado"
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106
msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." msgid "Custom feeds built by the community bring you new experiences and help you find the content you love."
msgstr "" msgstr "Feeds customizados feitos pela comunidade te proporcionam novas experiências e te ajudam a encontrar o conteúdo que você mais ama."
#: src/view/screens/PreferencesExternalEmbeds.tsx:55 #: src/view/screens/PreferencesExternalEmbeds.tsx:55
msgid "Customize media from external sites." msgid "Customize media from external sites."
@@ -1057,11 +998,7 @@ msgstr "Modo escuro"
#: src/view/screens/Settings.tsx:492 #: src/view/screens/Settings.tsx:492
msgid "Dark Theme" msgid "Dark Theme"
msgstr "" msgstr "Modo Escuro"
#: src/Navigation.tsx:204
#~ msgid "Debug"
#~ msgstr "Depuração"
#: src/view/screens/Debug.tsx:83 #: src/view/screens/Debug.tsx:83
msgid "Debug panel" msgid "Debug panel"
@@ -1095,7 +1032,7 @@ msgstr "Excluir minha conta"
#: src/view/screens/Settings.tsx:755 #: src/view/screens/Settings.tsx:755
msgid "Delete My Account…" msgid "Delete My Account…"
msgstr "" msgstr "Excluir minha conta…"
#: src/view/com/util/forms/PostDropdownBtn.tsx:228 #: src/view/com/util/forms/PostDropdownBtn.tsx:228
msgid "Delete post" msgid "Delete post"
@@ -1120,10 +1057,6 @@ msgstr "Post excluído."
msgid "Description" msgid "Description"
msgstr "Descrição" msgstr "Descrição"
#: src/view/com/auth/create/Step1.tsx:96
#~ msgid "Dev Server"
#~ msgstr "Servidor de Desenvolvimento"
#: src/view/screens/Settings.tsx:760 #: src/view/screens/Settings.tsx:760
msgid "Developer Tools" msgid "Developer Tools"
msgstr "Ferramentas de Desenvolvedor" msgstr "Ferramentas de Desenvolvedor"
@@ -1134,7 +1067,7 @@ msgstr "Você gostaria de dizer alguma coisa?"
#: src/view/screens/Settings.tsx:498 #: src/view/screens/Settings.tsx:498
msgid "Dim" msgid "Dim"
msgstr "" msgstr "Menos escuro"
#: src/view/com/composer/Composer.tsx:144 #: src/view/com/composer/Composer.tsx:144
msgid "Discard" msgid "Discard"
@@ -1366,13 +1299,9 @@ msgstr "Insira um nome para esta Senha de Aplicativo"
msgid "Enter Confirmation Code" msgid "Enter Confirmation Code"
msgstr "Insira o código de confirmação" msgstr "Insira o código de confirmação"
#: src/view/com/auth/create/Step1.tsx:71
#~ msgid "Enter the address of your provider:"
#~ msgstr "Digite o endereço do seu provedor:"
#: src/view/com/modals/ChangePassword.tsx:151 #: src/view/com/modals/ChangePassword.tsx:151
msgid "Enter the code you received to change your password." msgid "Enter the code you received to change your password."
msgstr "" msgstr "Digite o código recebido para alterar sua senha."
#: src/view/com/modals/ChangeHandle.tsx:371 #: src/view/com/modals/ChangeHandle.tsx:371
msgid "Enter the domain you want to use" msgid "Enter the domain you want to use"
@@ -1602,14 +1531,6 @@ msgstr "Seguir Todas"
msgid "Follow selected accounts and continue to the next step" msgid "Follow selected accounts and continue to the next step"
msgstr "Siga algumas contas e continue para o próximo passo" msgstr "Siga algumas contas e continue para o próximo passo"
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174
#~ msgid "Follow selected accounts and continue to then next step"
#~ msgstr "Siga algumas contas e continue para o próximo passo"
#: src/view/com/auth/onboarding/RecommendedFollows.tsx:42
#~ msgid "Follow some"
#~ msgstr "Siga alguns"
#: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 #: src/view/com/auth/onboarding/RecommendedFollows.tsx:64
msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." msgid "Follow some users to get started. We can recommend you more users based on who you find interesting."
msgstr "Comece seguindo alguns usuários. Mais usuários podem ser recomendados com base em quem você acha interessante." msgstr "Comece seguindo alguns usuários. Mais usuários podem ser recomendados com base em quem você acha interessante."
@@ -1634,10 +1555,6 @@ msgstr "seguiu você"
msgid "Followers" msgid "Followers"
msgstr "Seguidores" msgstr "Seguidores"
#: src/view/com/profile/ProfileHeader.tsx:624
#~ msgid "following"
#~ msgstr "seguindo"
#: src/view/com/profile/ProfileHeader.tsx:534 #: src/view/com/profile/ProfileHeader.tsx:534
#: src/view/screens/ProfileFollows.tsx:25 #: src/view/screens/ProfileFollows.tsx:25
msgid "Following" msgid "Following"
@@ -1743,10 +1660,6 @@ msgstr "Ajuda"
msgid "Here are some accounts for you to follow" msgid "Here are some accounts for you to follow"
msgstr "Aqui estão algumas contas para você seguir" msgstr "Aqui estão algumas contas para você seguir"
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132
#~ msgid "Here are some accounts for your to follow"
#~ msgstr "Aqui estão algumas contas para você seguir"
#: src/screens/Onboarding/StepTopicalFeeds.tsx:79 #: src/screens/Onboarding/StepTopicalFeeds.tsx:79
msgid "Here are some popular topical feeds. You can choose to follow as many as you like." msgid "Here are some popular topical feeds. You can choose to follow as many as you like."
msgstr "Aqui estão alguns feeds de assuntos. Você pode seguir quantos quiser." msgstr "Aqui estão alguns feeds de assuntos. Você pode seguir quantos quiser."
@@ -1793,10 +1706,6 @@ msgstr "Ocultar lista de usuários"
msgid "Hides posts from {0} in your feed" msgid "Hides posts from {0} in your feed"
msgstr "Esconder posts de {0} no seu feed" msgstr "Esconder posts de {0} no seu feed"
#: src/view/com/posts/FeedErrorMessage.tsx:102
#~ msgid "Hmm, some kind of issue occured when contacting the feed server. Please let the feed owner know about this issue."
#~ msgstr "Hmm, ocorreu algum problema ao entrar em contato com o servidor deste feed. Por favor, avise o criador do feed sobre este problema."
#: src/view/com/posts/FeedErrorMessage.tsx:111 #: src/view/com/posts/FeedErrorMessage.tsx:111
msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue."
msgstr "Hmm, ocorreu algum problema ao entrar em contato com o servidor deste feed. Por favor, avise o criador do feed sobre este problema." msgstr "Hmm, ocorreu algum problema ao entrar em contato com o servidor deste feed. Por favor, avise o criador do feed sobre este problema."
@@ -1817,10 +1726,6 @@ msgstr "Hmm, o servidor do feed teve algum problema. Por favor, avise o criador
msgid "Hmm, we're having trouble finding this feed. It may have been deleted." msgid "Hmm, we're having trouble finding this feed. It may have been deleted."
msgstr "Hmm, estamos com problemas para encontrar este feed. Ele pode ter sido excluído." msgstr "Hmm, estamos com problemas para encontrar este feed. Ele pode ter sido excluído."
#: src/view/com/posts/FeedErrorMessage.tsx:87
#~ msgid "Hmmm, we're having trouble finding this feed. It may have been deleted."
#~ msgstr "Hmm, estamos com problemas para encontrar este feed. Ele pode ter sido excluído."
#: src/Navigation.tsx:433 #: src/Navigation.tsx:433
#: src/view/shell/bottom-bar/BottomBar.tsx:137 #: src/view/shell/bottom-bar/BottomBar.tsx:137
#: src/view/shell/desktop/LeftNav.tsx:306 #: src/view/shell/desktop/LeftNav.tsx:306
@@ -1840,11 +1745,6 @@ msgstr "Preferências da Página Inicial"
msgid "Hosting provider" msgid "Hosting provider"
msgstr "Provedor de hospedagem" msgstr "Provedor de hospedagem"
#: src/view/com/auth/create/Step1.tsx:76
#: src/view/com/auth/create/Step1.tsx:81
#~ msgid "Hosting provider address"
#~ msgstr "Endereço do provedor de hospedagem"
#: src/view/com/modals/InAppBrowserConsent.tsx:44 #: src/view/com/modals/InAppBrowserConsent.tsx:44
msgid "How should we open this link?" msgid "How should we open this link?"
msgstr "Como devemos abrir este link?" msgstr "Como devemos abrir este link?"
@@ -1871,7 +1771,7 @@ msgstr "Se nenhum for selecionado, adequado para todas as idades."
#: src/view/com/modals/ChangePassword.tsx:146 #: src/view/com/modals/ChangePassword.tsx:146
msgid "If you want to change your password, we will send you a code to verify that this is your account." msgid "If you want to change your password, we will send you a code to verify that this is your account."
msgstr "" msgstr "Se você quiser alterar sua senha, enviaremos um código que para verificar sua identidade."
#: src/view/com/util/images/Gallery.tsx:38 #: src/view/com/util/images/Gallery.tsx:38
msgid "Image" msgid "Image"
@@ -1886,11 +1786,6 @@ msgstr "Texto alternativo da imagem"
msgid "Image options" msgid "Image options"
msgstr "Opções de imagem" msgstr "Opções de imagem"
#: src/view/com/search/Suggestions.tsx:104
#: src/view/com/search/Suggestions.tsx:115
#~ msgid "In Your Network"
#~ msgstr "Na sua rede"
#: src/view/com/auth/login/SetNewPasswordForm.tsx:138 #: src/view/com/auth/login/SetNewPasswordForm.tsx:138
msgid "Input code sent to your email for password reset" msgid "Input code sent to your email for password reset"
msgstr "Insira o código enviado para o seu e-mail para redefinir sua senha" msgstr "Insira o código enviado para o seu e-mail para redefinir sua senha"
@@ -1903,14 +1798,6 @@ msgstr "Insira o código de confirmação para excluir sua conta"
msgid "Input email for Bluesky account" msgid "Input email for Bluesky account"
msgstr "Insira o e-mail para a sua conta do Bluesky" msgstr "Insira o e-mail para a sua conta do Bluesky"
#: src/view/com/auth/create/Step2.tsx:109
#~ msgid "Input email for Bluesky waitlist"
#~ msgstr "Insira o e-mail para entrar na lista de espera do Bluesky"
#: src/view/com/auth/create/Step1.tsx:80
#~ msgid "Input hosting provider address"
#~ msgstr "Insira o endereço do provedor de hospedagem"
#: src/view/com/auth/create/Step1.tsx:102 #: src/view/com/auth/create/Step1.tsx:102
msgid "Input invite code to proceed" msgid "Input invite code to proceed"
msgstr "Insira o convite para continuar" msgstr "Insira o convite para continuar"
@@ -2124,14 +2011,6 @@ msgstr "Curtido por {likeCount} {0}"
msgid "liked your custom feed" msgid "liked your custom feed"
msgstr "curtiram seu feed" msgstr "curtiram seu feed"
#: src/view/com/notifications/FeedItem.tsx:171
#~ msgid "liked your custom feed '{0}'"
#~ msgstr "curtiram seu feed '{0}'"
#: src/view/com/notifications/FeedItem.tsx:171
#~ msgid "liked your custom feed{0}"
#~ msgstr "curtiu seu feed"
#: src/view/com/notifications/FeedItem.tsx:155 #: src/view/com/notifications/FeedItem.tsx:155
msgid "liked your post" msgid "liked your post"
msgstr "curtiu seu post" msgstr "curtiu seu post"
@@ -2144,14 +2023,6 @@ msgstr "Curtidas"
msgid "Likes on this post" msgid "Likes on this post"
msgstr "Curtidas neste post" msgstr "Curtidas neste post"
#: src/view/screens/Moderation.tsx:203
#~ msgid "Limit the visibility of my account"
#~ msgstr "Limitar a visibilidade do meu perfil"
#: src/view/screens/Moderation.tsx:203
#~ msgid "Limit the visibility of my account to logged-out users"
#~ msgstr "Limitar a visibilidade do meu perfil para usuários deslogados"
#: src/Navigation.tsx:167 #: src/Navigation.tsx:167
msgid "List" msgid "List"
msgstr "Lista" msgstr "Lista"
@@ -2231,10 +2102,6 @@ msgstr "Registros"
msgid "Log out" msgid "Log out"
msgstr "Sair" msgstr "Sair"
#: src/view/screens/Moderation.tsx:134
#~ msgid "Logged-out users"
#~ msgstr "Visibilidade do seu perfil"
#: src/view/screens/Moderation.tsx:136 #: src/view/screens/Moderation.tsx:136
msgid "Logged-out visibility" msgid "Logged-out visibility"
msgstr "Visibilidade do seu perfil" msgstr "Visibilidade do seu perfil"
@@ -2243,10 +2110,6 @@ msgstr "Visibilidade do seu perfil"
msgid "Login to account that is not listed" msgid "Login to account that is not listed"
msgstr "Fazer login em uma conta que não está listada" msgstr "Fazer login em uma conta que não está listada"
#: src/view/screens/ProfileFeed.tsx:472
#~ msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!"
#~ msgstr "Parece que este feed só está disponível para usuários com uma conta do Bluesky. Por favor, cadastre-se ou entre para ver este feed!"
#: src/view/com/modals/LinkWarning.tsx:65 #: src/view/com/modals/LinkWarning.tsx:65
msgid "Make sure this is where you intend to go!" msgid "Make sure this is where you intend to go!"
msgstr "Certifique-se de onde está indo!" msgstr "Certifique-se de onde está indo!"
@@ -2268,10 +2131,6 @@ msgstr "Usuários mencionados"
msgid "Menu" msgid "Menu"
msgstr "Menu" msgstr "Menu"
#: src/view/com/posts/FeedErrorMessage.tsx:194
#~ msgid "Message from server"
#~ msgstr "Mensagem do servidor"
#: src/view/com/posts/FeedErrorMessage.tsx:197 #: src/view/com/posts/FeedErrorMessage.tsx:197
msgid "Message from server: {0}" msgid "Message from server: {0}"
msgstr "Mensagem do servidor: {0}" msgstr "Mensagem do servidor: {0}"
@@ -2388,10 +2247,6 @@ msgstr "Contas silenciadas não aparecem no seu feed ou nas suas notificações.
msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them."
msgstr "Silenciar é privado. Contas silenciadas podem interagir com você, mas você não verá postagens ou receber notificações delas." msgstr "Silenciar é privado. Contas silenciadas podem interagir com você, mas você não verá postagens ou receber notificações delas."
#: src/view/screens/Moderation.tsx:134
#~ msgid "My Account"
#~ msgstr "Minha Conta"
#: src/view/com/modals/BirthDateSettings.tsx:56 #: src/view/com/modals/BirthDateSettings.tsx:56
msgid "My Birthday" msgid "My Birthday"
msgstr "Meu Aniversário" msgstr "Meu Aniversário"
@@ -2466,7 +2321,7 @@ msgstr "Nova senha"
#: src/view/com/modals/ChangePassword.tsx:215 #: src/view/com/modals/ChangePassword.tsx:215
msgid "New Password" msgid "New Password"
msgstr "" msgstr "Nova Senha"
#: src/view/com/feeds/FeedPage.tsx:201 #: src/view/com/feeds/FeedPage.tsx:201
msgctxt "action" msgctxt "action"
@@ -2487,10 +2342,6 @@ msgctxt "action"
msgid "New Post" msgid "New Post"
msgstr "Novo Post" msgstr "Novo Post"
#: src/view/shell/desktop/LeftNav.tsx:258
#~ msgid "New Post"
#~ msgstr "Novo Post"
#: src/view/com/modals/CreateOrEditList.tsx:247 #: src/view/com/modals/CreateOrEditList.tsx:247
msgid "New User List" msgid "New User List"
msgstr "Nova lista de usuários" msgstr "Nova lista de usuários"
@@ -2555,11 +2406,6 @@ msgstr "Nenhum resultado"
msgid "No results found for \"{query}\"" msgid "No results found for \"{query}\""
msgstr "Nenhum resultado encontrado para \"{query}\"" msgstr "Nenhum resultado encontrado para \"{query}\""
#: src/view/com/modals/ListAddUser.tsx:142
#: src/view/shell/desktop/Search.tsx:112
#~ msgid "No results found for {0}"
#~ msgstr "Nenhum resultado encontrado para {0}"
#: src/view/com/modals/ListAddRemoveUsers.tsx:127 #: src/view/com/modals/ListAddRemoveUsers.tsx:127
#: src/view/screens/Search/Search.tsx:280 #: src/view/screens/Search/Search.tsx:280
#: src/view/screens/Search/Search.tsx:308 #: src/view/screens/Search/Search.tsx:308
@@ -2574,10 +2420,6 @@ msgstr "Não, obrigado"
msgid "Nobody" msgid "Nobody"
msgstr "Ninguém" msgstr "Ninguém"
#: src/view/com/modals/SelfLabel.tsx:136
#~ msgid "Not Applicable"
#~ msgstr "Não Aplicável"
#: src/view/com/modals/SelfLabel.tsx:135 #: src/view/com/modals/SelfLabel.tsx:135
msgid "Not Applicable." msgid "Not Applicable."
msgstr "Não Aplicável." msgstr "Não Aplicável."
@@ -2591,18 +2433,10 @@ msgstr "Não encontrado"
msgid "Not right now" msgid "Not right now"
msgstr "Agora não" msgstr "Agora não"
#: src/view/screens/Moderation.tsx:227
#~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users."
#~ msgstr "Nota: o Bluesky é uma rede aberta e pública. Habilitar esta configuração não tornará seu perfil privado nem impedirá os usuários logados de verem os seus posts. Esta configuração só limita a visibilidade dos posts nos apps oficiais do Bluesky; aplicativos de terceiros podem não respeitá-la e poderão mostrar seu conteúdo para usuários deslogados."
#: src/view/screens/Moderation.tsx:233 #: src/view/screens/Moderation.tsx:233
msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites." msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites."
msgstr "Nota: o Bluesky é uma rede aberta e pública. Esta configuração limita somente a visibilidade do seu conteúdo no site e aplicativo do Bluesky, e outros aplicativos podem não respeitar esta configuração. Seu conteúdo ainda poderá ser exibido para usuários deslogados por outros aplicativos e sites." msgstr "Nota: o Bluesky é uma rede aberta e pública. Esta configuração limita somente a visibilidade do seu conteúdo no site e aplicativo do Bluesky, e outros aplicativos podem não respeitar esta configuração. Seu conteúdo ainda poderá ser exibido para usuários deslogados por outros aplicativos e sites."
#: src/view/screens/Moderation.tsx:227
#~ msgid "Note: Third-party apps that display Bluesky content may not respect this setting."
#~ msgstr "Nota: Aplicativos de terceiros que mostram conteúdo do Bluesky podem não respeitar esta opção."
#: src/Navigation.tsx:448 #: src/Navigation.tsx:448
#: src/view/screens/Notifications.tsx:120 #: src/view/screens/Notifications.tsx:120
#: src/view/screens/Notifications.tsx:144 #: src/view/screens/Notifications.tsx:144
@@ -2778,10 +2612,6 @@ msgstr "Opção {0} de {numItems}"
msgid "Or combine these options:" msgid "Or combine these options:"
msgstr "Ou combine estas opções:" msgstr "Ou combine estas opções:"
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:122
#~ msgid "Or you can try our \"Discover\" algorithm:"
#~ msgstr "Ou você pode tentar nosso algoritmo \"Discover\":"
#: src/view/com/auth/login/ChooseAccountForm.tsx:138 #: src/view/com/auth/login/ChooseAccountForm.tsx:138
msgid "Other account" msgid "Other account"
msgstr "Outra conta" msgstr "Outra conta"
@@ -2945,12 +2775,6 @@ msgctxt "description"
msgid "Post" msgid "Post"
msgstr "Post" msgstr "Post"
#: src/view/com/composer/Composer.tsx:346
#: src/view/com/post-thread/PostThread.tsx:225
#: src/view/screens/PostThread.tsx:80
#~ msgid "Post"
#~ msgstr "Post"
#: src/view/com/post-thread/PostThreadItem.tsx:177 #: src/view/com/post-thread/PostThreadItem.tsx:177
msgid "Post by {0}" msgid "Post by {0}"
msgstr "Post por {0}" msgstr "Post por {0}"
@@ -3071,10 +2895,6 @@ msgctxt "action"
msgid "Quote Post" msgid "Quote Post"
msgstr "Citar Post" msgstr "Citar Post"
#: src/view/com/modals/Repost.tsx:56
#~ msgid "Quote Post"
#~ msgstr "Citar Post"
#: src/view/screens/PreferencesThreads.tsx:86 #: src/view/screens/PreferencesThreads.tsx:86
msgid "Random (aka \"Poster's Roulette\")" msgid "Random (aka \"Poster's Roulette\")"
msgstr "Aleatório" msgstr "Aleatório"
@@ -3083,11 +2903,6 @@ msgstr "Aleatório"
msgid "Ratios" msgid "Ratios"
msgstr "Índices" msgstr "Índices"
#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:73
#: src/view/com/auth/onboarding/RecommendedFollows.tsx:50
#~ msgid "Recommended"
#~ msgstr "Recomendados"
#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:116 #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:116
msgid "Recommended Feeds" msgid "Recommended Feeds"
msgstr "Feeds Recomendados" msgstr "Feeds Recomendados"
@@ -3220,10 +3035,6 @@ msgstr "Repostar"
msgid "Repost or quote post" msgid "Repost or quote post"
msgstr "Repostar ou citar um post" msgstr "Repostar ou citar um post"
#: src/view/screens/PostRepostedBy.tsx:27
#~ msgid "Reposted by"
#~ msgstr "Repostado por"
#: src/view/screens/PostRepostedBy.tsx:27 #: src/view/screens/PostRepostedBy.tsx:27
msgid "Reposted By" msgid "Reposted By"
msgstr "Repostado Por" msgstr "Repostado Por"
@@ -3232,10 +3043,6 @@ msgstr "Repostado Por"
msgid "Reposted by {0}" msgid "Reposted by {0}"
msgstr "Repostado por {0}" msgstr "Repostado por {0}"
#: src/view/com/posts/FeedItem.tsx:206
#~ msgid "Reposted by {0})"
#~ msgstr "Repostado por {0})"
#: src/view/com/posts/FeedItem.tsx:224 #: src/view/com/posts/FeedItem.tsx:224
msgid "Reposted by <0/>" msgid "Reposted by <0/>"
msgstr "Repostado por <0/>" msgstr "Repostado por <0/>"
@@ -3260,11 +3067,7 @@ msgstr "Solicitar código"
#: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:239
#: src/view/com/modals/ChangePassword.tsx:241 #: src/view/com/modals/ChangePassword.tsx:241
msgid "Request Code" msgid "Request Code"
msgstr "" msgstr "Solicitar Código"
#: src/view/screens/Moderation.tsx:188
#~ msgid "Request to limit the visibility of my account"
#~ msgstr "Exigir limitação de visibilidade da minha conta"
#: src/view/screens/Settings.tsx:450 #: src/view/screens/Settings.tsx:450
msgid "Require alt text before posting" msgid "Require alt text before posting"
@@ -3281,7 +3084,7 @@ msgstr "Código de redefinição"
#: src/view/com/modals/ChangePassword.tsx:190 #: src/view/com/modals/ChangePassword.tsx:190
msgid "Reset Code" msgid "Reset Code"
msgstr "" msgstr "Código de Redefinição"
#: src/view/screens/Settings.tsx:806 #: src/view/screens/Settings.tsx:806
msgid "Reset onboarding" msgid "Reset onboarding"
@@ -3332,10 +3135,6 @@ msgstr "Tenta a última ação, que deu erro"
msgid "Retry" msgid "Retry"
msgstr "Tente novamente" msgstr "Tente novamente"
#: src/view/com/modals/ChangeHandle.tsx:169
#~ msgid "Retry change handle"
#~ msgstr "Tentar troca de usuário novamente"
#: src/view/com/auth/create/Step2.tsx:247 #: src/view/com/auth/create/Step2.tsx:247
msgid "Retry." msgid "Retry."
msgstr "Tentar novamente." msgstr "Tentar novamente."
@@ -3367,10 +3166,6 @@ msgstr "Salvar"
msgid "Save alt text" msgid "Save alt text"
msgstr "Salvar texto alternativo" msgstr "Salvar texto alternativo"
#: src/view/com/modals/UserAddRemoveLists.tsx:212
#~ msgid "Save changes"
#~ msgstr "Salvar alterações"
#: src/view/com/modals/EditProfile.tsx:232 #: src/view/com/modals/EditProfile.tsx:232
msgid "Save Changes" msgid "Save Changes"
msgstr "Salvar Alterações" msgstr "Salvar Alterações"
@@ -3425,10 +3220,6 @@ msgstr "Buscar"
msgid "Search for \"{query}\"" msgid "Search for \"{query}\""
msgstr "Pesquisar por \"{query}\"" msgstr "Pesquisar por \"{query}\""
#: src/view/screens/Search/Search.tsx:390
#~ msgid "Search for posts and users."
#~ msgstr "Buscar por posts e usuários."
#: src/view/com/auth/LoggedOut.tsx:104 #: src/view/com/auth/LoggedOut.tsx:104
#: src/view/com/auth/LoggedOut.tsx:105 #: src/view/com/auth/LoggedOut.tsx:105
#: src/view/com/modals/ListAddRemoveUsers.tsx:70 #: src/view/com/modals/ListAddRemoveUsers.tsx:70
@@ -3526,10 +3317,6 @@ msgctxt "action"
msgid "Send Email" msgid "Send Email"
msgstr "Enviar E-mail" msgstr "Enviar E-mail"
#: src/view/com/modals/DeleteAccount.tsx:138
#~ msgid "Send Email"
#~ msgstr "Enviar Email"
#: src/view/shell/Drawer.tsx:298 #: src/view/shell/Drawer.tsx:298
#: src/view/shell/Drawer.tsx:319 #: src/view/shell/Drawer.tsx:319
msgid "Send feedback" msgid "Send feedback"
@@ -3567,11 +3354,11 @@ msgstr "Definir o tema para acompanhar o sistema"
#: src/view/screens/Settings.tsx:508 #: src/view/screens/Settings.tsx:508
msgid "Set dark theme to the dark theme" msgid "Set dark theme to the dark theme"
msgstr "" msgstr "Definir o tema escuro para o padrão"
#: src/view/screens/Settings.tsx:501 #: src/view/screens/Settings.tsx:501
msgid "Set dark theme to the dim theme" msgid "Set dark theme to the dim theme"
msgstr "" msgstr "Definir o tema escuro para a versão menos escura"
#: src/view/com/auth/login/SetNewPasswordForm.tsx:104 #: src/view/com/auth/login/SetNewPasswordForm.tsx:104
msgid "Set new password" msgid "Set new password"
@@ -3617,10 +3404,6 @@ msgstr "Configura o e-mail para recuperação de senha"
msgid "Sets hosting provider for password reset" msgid "Sets hosting provider for password reset"
msgstr "Configura o provedor de hospedagem para recuperação de senha" msgstr "Configura o provedor de hospedagem para recuperação de senha"
#: src/view/com/auth/create/Step1.tsx:143
#~ msgid "Sets hosting provider to {label}"
#~ msgstr "Configura o provedor de hospedagem para {label}"
#: src/view/com/auth/create/Step1.tsx:78 #: src/view/com/auth/create/Step1.tsx:78
#: src/view/com/auth/login/LoginForm.tsx:148 #: src/view/com/auth/login/LoginForm.tsx:148
msgid "Sets server for the Bluesky client" msgid "Sets server for the Bluesky client"
@@ -3653,10 +3436,6 @@ msgstr "Compartilhar"
msgid "Share feed" msgid "Share feed"
msgstr "Compartilhar feed" msgstr "Compartilhar feed"
#: src/view/screens/ProfileFeed.tsx:276
#~ msgid "Share link"
#~ msgstr "Compartilhar link"
#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:43 #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:43
#: src/view/com/modals/ContentFilteringSettings.tsx:261 #: src/view/com/modals/ContentFilteringSettings.tsx:261
#: src/view/com/util/moderation/ContentHider.tsx:107 #: src/view/com/util/moderation/ContentHider.tsx:107
@@ -3882,10 +3661,6 @@ msgstr "Página de status"
msgid "Step {0} of {numSteps}" msgid "Step {0} of {numSteps}"
msgstr "Passo {0} de {numSteps}" msgstr "Passo {0} de {numSteps}"
#: src/view/com/auth/create/StepHeader.tsx:15
#~ msgid "Step {step} of 3"
#~ msgstr "Passo {step} de 3"
#: src/view/screens/Settings.tsx:276 #: src/view/screens/Settings.tsx:276
msgid "Storage cleared, you need to restart the app now." msgid "Storage cleared, you need to restart the app now."
msgstr "Armazenamento limpo, você precisa reiniciar o app agora." msgstr "Armazenamento limpo, você precisa reiniciar o app agora."
@@ -3912,10 +3687,6 @@ msgstr "Increver-se no feed {0}"
msgid "Subscribe to this list" msgid "Subscribe to this list"
msgstr "Inscreva-se nesta lista" msgstr "Inscreva-se nesta lista"
#: src/view/com/lists/ListCard.tsx:101
#~ msgid "Subscribed"
#~ msgstr "Inscrito"
#: src/view/screens/Search/Search.tsx:373 #: src/view/screens/Search/Search.tsx:373
msgid "Suggested Follows" msgid "Suggested Follows"
msgstr "Sugestões de Seguidores" msgstr "Sugestões de Seguidores"
@@ -4016,10 +3787,6 @@ msgstr "A Política de Privacidade foi movida para <0/>"
msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us." msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us."
msgstr "O formulário de suporte foi movido. Se precisar de ajuda, <0/> ou visite {HELP_DESK_URL} para entrar em contato conosco." msgstr "O formulário de suporte foi movido. Se precisar de ajuda, <0/> ou visite {HELP_DESK_URL} para entrar em contato conosco."
#: src/view/screens/Support.tsx:36
#~ msgid "The support form has been moved. If you need help, please<0/> or visit {HELP_DESK_URL} to get in touch with us."
#~ msgstr "O formulário de suporte foi movido. Se precisar de ajuda, por favor <0/> ou visite {HELP_DESK_URL} para entrar em contato conosco."
#: src/view/screens/TermsOfService.tsx:33 #: src/view/screens/TermsOfService.tsx:33
msgid "The Terms of Service have been moved to" msgid "The Terms of Service have been moved to"
msgstr "Os Termos de Serviço foram movidos para" msgstr "Os Termos de Serviço foram movidos para"
@@ -4114,14 +3881,6 @@ msgstr "Houve um problema com este número. Por favor, escolha um país e digite
msgid "These are popular accounts you might like:" msgid "These are popular accounts you might like:"
msgstr "Estas são contas populares que talvez você goste:" msgstr "Estas são contas populares que talvez você goste:"
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138
#~ msgid "These are popular accounts you might like."
#~ msgstr "Estas são contas populares que talvez você goste."
#: src/view/com/util/moderation/LabelInfo.tsx:45
#~ msgid "This {0} has been labeled."
#~ msgstr "Este {0} foi reportado."
#: src/view/com/util/moderation/ScreenHider.tsx:88 #: src/view/com/util/moderation/ScreenHider.tsx:88
msgid "This {screenDescription} has been flagged:" msgid "This {screenDescription} has been flagged:"
msgstr "Este {screenDescription} foi reportado:" msgstr "Este {screenDescription} foi reportado:"
@@ -4164,10 +3923,6 @@ msgstr "Esta informação não é compartilhada com outros usuários."
msgid "This is important in case you ever need to change your email or reset your password." msgid "This is important in case you ever need to change your email or reset your password."
msgstr "Isso é importante caso você precise alterar seu e-mail ou redefinir sua senha." msgstr "Isso é importante caso você precise alterar seu e-mail ou redefinir sua senha."
#: src/view/com/auth/create/Step1.tsx:55
#~ msgid "This is the service that keeps you online."
#~ msgstr "Este é o serviço que o mantém online."
#: src/view/com/modals/LinkWarning.tsx:58 #: src/view/com/modals/LinkWarning.tsx:58
msgid "This link is taking you to the following website:" msgid "This link is taking you to the following website:"
msgstr "Este link está levando você ao seguinte site:" msgstr "Este link está levando você ao seguinte site:"
@@ -4194,7 +3949,7 @@ msgstr "Este usuário está incluído na lista <0/>, que você bloqueou."
#: src/view/com/modals/ModerationDetails.tsx:74 #: src/view/com/modals/ModerationDetails.tsx:74
msgid "This user is included in the <0/> list which you have muted." msgid "This user is included in the <0/> list which you have muted."
msgstr "" msgstr "Este usuário está incluído na lista <0/>, que você silenciou."
#: src/view/com/modals/ModerationDetails.tsx:74 #: src/view/com/modals/ModerationDetails.tsx:74
#~ msgid "This user is included the <0/> list which you have muted." #~ msgid "This user is included the <0/> list which you have muted."
@@ -4240,10 +3995,6 @@ msgctxt "action"
msgid "Try again" msgid "Try again"
msgstr "Tentar novamente" msgstr "Tentar novamente"
#: src/view/com/util/error/ErrorScreen.tsx:73
#~ msgid "Try again"
#~ msgstr "Tente novamente"
#: src/view/screens/ProfileList.tsx:505 #: src/view/screens/ProfileList.tsx:505
msgid "Un-block list" msgid "Un-block list"
msgstr "Desbloquear lista" msgstr "Desbloquear lista"
@@ -4426,10 +4177,6 @@ msgstr "Usuários"
msgid "users followed by <0/>" msgid "users followed by <0/>"
msgstr "usuários seguidos por <0/>" msgstr "usuários seguidos por <0/>"
#: src/view/com/threadgate/WhoCanReply.tsx:115
#~ msgid "Users followed by <0/>"
#~ msgstr "Usuários seguidos por <0/>"
#: src/view/com/modals/Threadgate.tsx:106 #: src/view/com/modals/Threadgate.tsx:106
msgid "Users in \"{0}\"" msgid "Users in \"{0}\""
msgstr "Usuários em \"{0}\"" msgstr "Usuários em \"{0}\""
@@ -4504,10 +4251,6 @@ msgstr "Estimamos que sua conta estará pronta em mais ou menos {estimatedTime}.
msgid "We hope you have a wonderful time. Remember, Bluesky is:" msgid "We hope you have a wonderful time. Remember, Bluesky is:"
msgstr "Esperamos que você se divirta. Lembre-se, o Bluesky é:" msgstr "Esperamos que você se divirta. Lembre-se, o Bluesky é:"
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
#~ msgid "We ran out of posts from your follows. Here's the latest from"
#~ msgstr "Não temos mais posts de quem você segue. Aqui estão os mais novos de"
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29 #: src/view/com/posts/DiscoverFallbackHeader.tsx:29
msgid "We ran out of posts from your follows. Here's the latest from <0/>." msgid "We ran out of posts from your follows. Here's the latest from <0/>."
msgstr "Não temos mais posts de quem você segue. Aqui estão os mais novos de <0/>." msgstr "Não temos mais posts de quem você segue. Aqui estão os mais novos de <0/>."
@@ -4540,14 +4283,6 @@ msgstr "Usaremos isto para customizar a sua experiência."
msgid "We're so excited to have you join us!" msgid "We're so excited to have you join us!"
msgstr "Estamos muito felizes em recebê-lo!" msgstr "Estamos muito felizes em recebê-lo!"
#: src/view/com/posts/FeedErrorMessage.tsx:99
#~ msgid "We're sorry, but this content is not viewable without a Bluesky account."
#~ msgstr "Desculpe, mas este conteúdo não é visível sem uma conta do Bluesky."
#: src/view/com/posts/FeedErrorMessage.tsx:105
#~ msgid "We're sorry, but this feed is currently receiving high traffic and is temporarily unavailable. Please try again later."
#~ msgstr "Desculpe, mas este feed está recebendo muito tráfego e está temporariamente indisponível. Por favor, tente novamente mais tarde."
#: src/view/screens/ProfileList.tsx:85 #: src/view/screens/ProfileList.tsx:85
msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}."
msgstr "Tivemos um problema ao exibir esta lista. Se continuar acontecendo, contate o criador da lista: @{handleOrDid}." msgstr "Tivemos um problema ao exibir esta lista. Se continuar acontecendo, contate o criador da lista: @{handleOrDid}."
@@ -4638,10 +4373,6 @@ msgstr "Você também pode descobrir novos feeds para seguir."
#~ msgid "You can also try our \"Discover\" algorithm:" #~ msgid "You can also try our \"Discover\" algorithm:"
#~ msgstr "Você também pode tentar nosso algoritmo \"Discover\":" #~ msgstr "Você também pode tentar nosso algoritmo \"Discover\":"
#: src/view/com/auth/create/Step1.tsx:106
#~ msgid "You can change hosting providers at any time."
#~ msgstr "Você pode alterar os provedores de hospedagem a qualquer momento."
#: src/screens/Onboarding/StepFollowingFeed.tsx:142 #: src/screens/Onboarding/StepFollowingFeed.tsx:142
msgid "You can change these settings later." msgid "You can change these settings later."
msgstr "Você pode mudar estas configurações depois." msgstr "Você pode mudar estas configurações depois."
@@ -4680,7 +4411,7 @@ msgstr "Você bloqueou este usuário. Você não pode ver este conteúdo."
#: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:87
#: src/view/com/modals/ChangePassword.tsx:121 #: src/view/com/modals/ChangePassword.tsx:121
msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." msgid "You have entered an invalid code. It should look like XXXXX-XXXXX."
msgstr "" msgstr "Você utilizou um código inválido. O código segue este padrão: XXXXX-XXXXX."
#: src/view/com/modals/ModerationDetails.tsx:87 #: src/view/com/modals/ModerationDetails.tsx:87
msgid "You have muted this user." msgid "You have muted this user."
@@ -4795,10 +4526,6 @@ msgstr "Seu identificador completo será"
msgid "Your full handle will be <0>@{0}</0>" msgid "Your full handle will be <0>@{0}</0>"
msgstr "Seu usuário completo será <0>@{0}</0>" msgstr "Seu usuário completo será <0>@{0}</0>"
#: src/view/com/auth/create/Step1.tsx:53
#~ msgid "Your hosting provider"
#~ msgstr "Seu provedor de hospedagem"
#: src/view/screens/Settings.tsx:430 #: src/view/screens/Settings.tsx:430
#: src/view/shell/desktop/RightNav.tsx:137 #: src/view/shell/desktop/RightNav.tsx:137
#: src/view/shell/Drawer.tsx:660 #: src/view/shell/Drawer.tsx:660
@@ -4807,7 +4534,7 @@ msgstr "Seus códigos de convite estão ocultos quando conectado com uma Senha d
#: src/view/com/modals/ChangePassword.tsx:155 #: src/view/com/modals/ChangePassword.tsx:155
msgid "Your password has been changed successfully!" msgid "Your password has been changed successfully!"
msgstr "" msgstr "Sua senha foi alterada com sucesso!"
#: src/view/com/composer/Composer.tsx:267 #: src/view/com/composer/Composer.tsx:267
msgid "Your post has been published" msgid "Your post has been published"
@@ -4824,18 +4551,6 @@ msgstr "Suas postagens, curtidas e bloqueios são públicos. Silenciamentos são
msgid "Your profile" msgid "Your profile"
msgstr "Seu perfil" msgstr "Seu perfil"
#: src/view/screens/Moderation.tsx:205
#~ msgid "Your profile and account will not be visible to anyone visiting the Bluesky app without an account, or to account holders who are not logged in. Enabling this will not make your profile private."
#~ msgstr "Seu perfil e conta não serão visíveis para pessoas utilizando o app Bluesky sem uma conta, ou para pessoas que têm conta mas estão deslogadas. Habilitar esta opção não torna a sua conta privada."
#: src/view/screens/Moderation.tsx:220
#~ msgid "Your profile and content will not be visible to anyone visiting the Bluesky app without an account. Enabling this will not make your profile private."
#~ msgstr "Seu perfil e seu conteúdo não serão visíveis para pessoas utilizando o app Bluesky sem uma conta. Habilitar esta opção não torna a sua conta privada."
#: src/view/screens/Moderation.tsx:220
#~ msgid "Your profile and posts will not be visible to people visiting the Bluesky app or website without having an account and being logged in."
#~ msgstr "Seu perfil e posts não serão visíveis para pessoas utilizando o app ou site do Bluesky sem uma conta logada."
#: src/view/com/composer/Composer.tsx:266 #: src/view/com/composer/Composer.tsx:266
msgid "Your reply has been published" msgid "Your reply has been published"
msgstr "Sua resposta foi publicada" msgstr "Sua resposta foi publicada"
+105 -105
View File
@@ -1,6 +1,6 @@
msgid "" msgid ""
msgstr "" msgstr ""
"POT-Creation-Date: 2024-02-01 09:20+0800\n" "POT-Creation-Date: 2024-02-07 19:20+0800\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@@ -9,8 +9,8 @@ msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n" "PO-Revision-Date: \n"
"Last-Translator: Frudrax Cheng <i@cynosura.one>\n" "Last-Translator: Mikan Harada <i@3kn.jp>\n"
"Language-Team: Frudrax Cheng, Simon Chan, U2FsdGVkX1\n" "Language-Team: Frudrax Cheng, Simon Chan, U2FsdGVkX1, Mikan Harada\n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/view/com/modals/VerifyEmail.tsx:142 #: src/view/com/modals/VerifyEmail.tsx:142
@@ -19,7 +19,7 @@ msgstr "(没有邮件)"
#: src/view/shell/desktop/RightNav.tsx:168 #: src/view/shell/desktop/RightNav.tsx:168
msgid "{0, plural, one {# invite code available} other {# invite codes available}}" msgid "{0, plural, one {# invite code available} other {# invite codes available}}"
msgstr "{0, plural, one {# 邀请码可用} other {# 邀请码可用}}" msgstr "{0, plural, one {# 邀请码可用} other {# 邀请码可用}}"
#: src/view/com/profile/ProfileHeader.tsx:632 #: src/view/com/profile/ProfileHeader.tsx:632
msgid "{following} following" msgid "{following} following"
@@ -32,12 +32,12 @@ msgstr "{invitesAvailable, plural, one {邀请码: # 可用} other {邀请码: #
#: src/view/screens/Settings.tsx:435 #: src/view/screens/Settings.tsx:435
#: src/view/shell/Drawer.tsx:664 #: src/view/shell/Drawer.tsx:664
msgid "{invitesAvailable} invite code available" msgid "{invitesAvailable} invite code available"
msgstr "{invitesAvailable} 邀请码可用" msgstr "{invitesAvailable} 邀请码可用"
#: src/view/screens/Settings.tsx:437 #: src/view/screens/Settings.tsx:437
#: src/view/shell/Drawer.tsx:666 #: src/view/shell/Drawer.tsx:666
msgid "{invitesAvailable} invite codes available" msgid "{invitesAvailable} invite codes available"
msgstr "{invitesAvailable} 邀请码可用" msgstr "{invitesAvailable} 邀请码可用"
#: src/view/shell/Drawer.tsx:443 #: src/view/shell/Drawer.tsx:443
msgid "{numUnreadNotifications} unread" msgid "{numUnreadNotifications} unread"
@@ -65,7 +65,7 @@ msgstr "<0>欢迎来到</0><1>Bluesky</1>"
#: src/view/com/profile/ProfileHeader.tsx:597 #: src/view/com/profile/ProfileHeader.tsx:597
msgid "⚠Invalid Handle" msgid "⚠Invalid Handle"
msgstr "⚠无效的昵称" msgstr "⚠无效的用户识别符"
#: src/view/com/util/moderation/LabelInfo.tsx:45 #: src/view/com/util/moderation/LabelInfo.tsx:45
msgid "A content warning has been applied to this {0}." msgid "A content warning has been applied to this {0}."
@@ -101,15 +101,15 @@ msgstr "已屏蔽账户"
#: src/view/com/profile/ProfileHeader.tsx:260 #: src/view/com/profile/ProfileHeader.tsx:260
msgid "Account muted" msgid "Account muted"
msgstr "已静音账户" msgstr "已隐藏账户"
#: src/view/com/modals/ModerationDetails.tsx:86 #: src/view/com/modals/ModerationDetails.tsx:86
msgid "Account Muted" msgid "Account Muted"
msgstr "已静音账户" msgstr "已隐藏账户"
#: src/view/com/modals/ModerationDetails.tsx:72 #: src/view/com/modals/ModerationDetails.tsx:72
msgid "Account Muted by List" msgid "Account Muted by List"
msgstr "账户已被列表静音" msgstr "账户已被列表隐藏"
#: src/view/com/util/AccountDropdownBtn.tsx:41 #: src/view/com/util/AccountDropdownBtn.tsx:41
msgid "Account options" msgid "Account options"
@@ -125,7 +125,7 @@ msgstr "已取消屏蔽账户"
#: src/view/com/profile/ProfileHeader.tsx:273 #: src/view/com/profile/ProfileHeader.tsx:273
msgid "Account unmuted" msgid "Account unmuted"
msgstr "已取消静音账户" msgstr "已取消隐藏账户"
#: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:150 #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:150
#: src/view/com/modals/ListAddRemoveUsers.tsx:264 #: src/view/com/modals/ListAddRemoveUsers.tsx:264
@@ -221,7 +221,7 @@ msgstr "详细设置"
#: src/view/com/auth/login/ForgotPasswordForm.tsx:217 #: src/view/com/auth/login/ForgotPasswordForm.tsx:217
#: src/view/com/modals/ChangePassword.tsx:168 #: src/view/com/modals/ChangePassword.tsx:168
msgid "Already have a code?" msgid "Already have a code?"
msgstr "" msgstr "已经有确认码了?"
#: src/view/com/auth/login/ChooseAccountForm.tsx:98 #: src/view/com/auth/login/ChooseAccountForm.tsx:98
msgid "Already signed in as @{0}" msgid "Already signed in as @{0}"
@@ -540,7 +540,7 @@ msgstr "撤销账户删除申请"
#: src/view/com/modals/ChangeHandle.tsx:149 #: src/view/com/modals/ChangeHandle.tsx:149
msgid "Cancel change handle" msgid "Cancel change handle"
msgstr "撤销修改昵称" msgstr "撤销修改用户识别符"
#: src/view/com/modals/crop-image/CropImage.web.tsx:134 #: src/view/com/modals/crop-image/CropImage.web.tsx:134
msgid "Cancel image crop" msgid "Cancel image crop"
@@ -570,12 +570,12 @@ msgstr "更改"
#: src/view/screens/Settings.tsx:690 #: src/view/screens/Settings.tsx:690
msgid "Change handle" msgid "Change handle"
msgstr "更改昵称" msgstr "更改用户识别符"
#: src/view/com/modals/ChangeHandle.tsx:161 #: src/view/com/modals/ChangeHandle.tsx:161
#: src/view/screens/Settings.tsx:699 #: src/view/screens/Settings.tsx:699
msgid "Change Handle" msgid "Change Handle"
msgstr "更改昵称" msgstr "更改用户识别符"
#: src/view/com/modals/VerifyEmail.tsx:147 #: src/view/com/modals/VerifyEmail.tsx:147
msgid "Change my email" msgid "Change my email"
@@ -583,11 +583,11 @@ msgstr "更改我的邮箱地址"
#: src/view/screens/Settings.tsx:726 #: src/view/screens/Settings.tsx:726
msgid "Change password" msgid "Change password"
msgstr "" msgstr "更改密码"
#: src/view/screens/Settings.tsx:735 #: src/view/screens/Settings.tsx:735
msgid "Change Password" msgid "Change Password"
msgstr "" msgstr "更改密码"
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73
msgid "Change post language to {0}" msgid "Change post language to {0}"
@@ -595,7 +595,7 @@ msgstr "更改帖子的发布语言至 {0}"
#: src/view/screens/Settings.tsx:727 #: src/view/screens/Settings.tsx:727
msgid "Change your Bluesky password" msgid "Change your Bluesky password"
msgstr "" msgstr "更改你的 Bluesky 密码"
#: src/view/com/modals/ChangeEmail.tsx:109 #: src/view/com/modals/ChangeEmail.tsx:109
msgid "Change Your Email" msgid "Change Your Email"
@@ -681,7 +681,7 @@ msgstr "气象"
#: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:265
#: src/view/com/modals/ChangePassword.tsx:268 #: src/view/com/modals/ChangePassword.tsx:268
msgid "Close" msgid "Close"
msgstr "" msgstr "关闭"
#: src/components/Dialog/index.web.tsx:78 #: src/components/Dialog/index.web.tsx:78
msgid "Close active dialog" msgid "Close active dialog"
@@ -969,7 +969,7 @@ msgstr "自定义域名"
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106
msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." msgid "Custom feeds built by the community bring you new experiences and help you find the content you love."
msgstr "" msgstr "由社区构建的自定义信息流能为你带来新的体验,并帮助你找到你喜欢的内容。"
#: src/view/screens/PreferencesExternalEmbeds.tsx:55 #: src/view/screens/PreferencesExternalEmbeds.tsx:55
msgid "Customize media from external sites." msgid "Customize media from external sites."
@@ -982,15 +982,15 @@ msgstr "自定义外部站点的媒体。"
#: src/view/screens/Settings.tsx:479 #: src/view/screens/Settings.tsx:479
#: src/view/screens/Settings.tsx:505 #: src/view/screens/Settings.tsx:505
msgid "Dark" msgid "Dark"
msgstr "暗色" msgstr "深黑"
#: src/view/screens/Debug.tsx:63 #: src/view/screens/Debug.tsx:63
msgid "Dark mode" msgid "Dark mode"
msgstr "色模式" msgstr "色模式"
#: src/view/screens/Settings.tsx:492 #: src/view/screens/Settings.tsx:492
msgid "Dark Theme" msgid "Dark Theme"
msgstr "" msgstr "深色模式"
#: src/view/screens/Debug.tsx:83 #: src/view/screens/Debug.tsx:83
msgid "Debug panel" msgid "Debug panel"
@@ -1024,7 +1024,7 @@ msgstr "删除我的账户"
#: src/view/screens/Settings.tsx:755 #: src/view/screens/Settings.tsx:755
msgid "Delete My Account…" msgid "Delete My Account…"
msgstr "" msgstr "删除我的账户…"
#: src/view/com/util/forms/PostDropdownBtn.tsx:228 #: src/view/com/util/forms/PostDropdownBtn.tsx:228
msgid "Delete post" msgid "Delete post"
@@ -1059,7 +1059,7 @@ msgstr "有什么想说的吗?"
#: src/view/screens/Settings.tsx:498 #: src/view/screens/Settings.tsx:498
msgid "Dim" msgid "Dim"
msgstr "" msgstr "暗淡"
#: src/view/com/composer/Composer.tsx:144 #: src/view/com/composer/Composer.tsx:144
msgid "Discard" msgid "Discard"
@@ -1137,7 +1137,7 @@ msgstr "拖放即可添加图片"
#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111
msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up."
msgstr "受 Apple 政策限制,成人内容只能在完成注册后在网页端启用显示" msgstr "受 Apple 政策限制,成人内容只能在完成注册后在网页端启用显示"
#: src/view/com/modals/EditProfile.tsx:185 #: src/view/com/modals/EditProfile.tsx:185
msgid "e.g. Alice Roberts" msgid "e.g. Alice Roberts"
@@ -1183,7 +1183,7 @@ msgstr "编辑列表详情"
#: src/view/com/modals/CreateOrEditList.tsx:250 #: src/view/com/modals/CreateOrEditList.tsx:250
msgid "Edit Moderation List" msgid "Edit Moderation List"
msgstr "编辑管理员列表" msgstr "编辑限制列表"
#: src/Navigation.tsx:243 #: src/Navigation.tsx:243
#: src/view/screens/Feeds.tsx:403 #: src/view/screens/Feeds.tsx:403
@@ -1293,7 +1293,7 @@ msgstr "输入验证码"
#: src/view/com/modals/ChangePassword.tsx:151 #: src/view/com/modals/ChangePassword.tsx:151
msgid "Enter the code you received to change your password." msgid "Enter the code you received to change your password."
msgstr "" msgstr "输入你收到的确认码以更改密码。"
#: src/view/com/modals/ChangeHandle.tsx:371 #: src/view/com/modals/ChangeHandle.tsx:371
msgid "Enter the domain you want to use" msgid "Enter the domain you want to use"
@@ -1342,7 +1342,7 @@ msgstr "所有人"
#: src/view/com/modals/ChangeHandle.tsx:150 #: src/view/com/modals/ChangeHandle.tsx:150
msgid "Exits handle change process" msgid "Exits handle change process"
msgstr "退出修改昵称流程" msgstr "退出修改用户识别符流程"
#: src/view/com/lightbox/Lightbox.web.tsx:120 #: src/view/com/lightbox/Lightbox.web.tsx:120
msgid "Exits image view" msgid "Exits image view"
@@ -1388,7 +1388,7 @@ msgstr "外部媒体设置"
#: src/view/com/modals/AddAppPasswords.tsx:115 #: src/view/com/modals/AddAppPasswords.tsx:115
#: src/view/com/modals/AddAppPasswords.tsx:119 #: src/view/com/modals/AddAppPasswords.tsx:119
msgid "Failed to create app password." msgid "Failed to create app password."
msgstr "无法创建 App 专用密码。" msgstr "创建 App 专用密码失败。"
#: src/view/com/modals/CreateOrEditList.tsx:206 #: src/view/com/modals/CreateOrEditList.tsx:206
msgid "Failed to create the list. Check your internet connection and try again." msgid "Failed to create the list. Check your internet connection and try again."
@@ -1633,7 +1633,7 @@ msgstr "转到下一个"
#: src/view/com/modals/ChangeHandle.tsx:265 #: src/view/com/modals/ChangeHandle.tsx:265
msgid "Handle" msgid "Handle"
msgstr "昵称" msgstr "用户识别符"
#: src/view/com/auth/create/CreateAccount.tsx:197 #: src/view/com/auth/create/CreateAccount.tsx:197
msgid "Having trouble?" msgid "Having trouble?"
@@ -1739,7 +1739,7 @@ msgstr "我们该如何打开此链接?"
#: src/view/com/modals/VerifyEmail.tsx:214 #: src/view/com/modals/VerifyEmail.tsx:214
msgid "I have a code" msgid "I have a code"
msgstr "我有邀请码" msgstr "我有验证码"
#: src/view/com/modals/VerifyEmail.tsx:216 #: src/view/com/modals/VerifyEmail.tsx:216
msgid "I have a confirmation code" msgid "I have a confirmation code"
@@ -1759,7 +1759,7 @@ msgstr "若不勾选,则默认为全年龄向。"
#: src/view/com/modals/ChangePassword.tsx:146 #: src/view/com/modals/ChangePassword.tsx:146
msgid "If you want to change your password, we will send you a code to verify that this is your account." msgid "If you want to change your password, we will send you a code to verify that this is your account."
msgstr "" msgstr "如果你想要更改密码,我们将向你发送一个确认码以验证这是你的账户。"
#: src/view/com/util/images/Gallery.tsx:38 #: src/view/com/util/images/Gallery.tsx:38
msgid "Image" msgid "Image"
@@ -1828,7 +1828,7 @@ msgstr "输入你的密码"
#: src/view/com/auth/create/Step3.tsx:42 #: src/view/com/auth/create/Step3.tsx:42
msgid "Input your user handle" msgid "Input your user handle"
msgstr "输入你的用户昵称" msgstr "输入你的用户识别符"
#: src/view/com/post-thread/PostThreadItem.tsx:231 #: src/view/com/post-thread/PostThreadItem.tsx:231
msgid "Invalid or unsupported post record" msgid "Invalid or unsupported post record"
@@ -1845,7 +1845,7 @@ msgstr "邀请"
#: src/view/com/modals/InviteCodes.tsx:93 #: src/view/com/modals/InviteCodes.tsx:93
#: src/view/screens/Settings.tsx:399 #: src/view/screens/Settings.tsx:399
msgid "Invite a Friend" msgid "Invite a Friend"
msgstr "邀请一位朋友" msgstr "邀请朋友"
#: src/view/com/auth/create/Step1.tsx:92 #: src/view/com/auth/create/Step1.tsx:92
#: src/view/com/auth/create/Step1.tsx:101 #: src/view/com/auth/create/Step1.tsx:101
@@ -2033,7 +2033,7 @@ msgstr "列表已删除"
#: src/view/screens/ProfileList.tsx:282 #: src/view/screens/ProfileList.tsx:282
msgid "List muted" msgid "List muted"
msgstr "列表已静音" msgstr "列表已隐藏"
#: src/view/com/modals/CreateOrEditList.tsx:275 #: src/view/com/modals/CreateOrEditList.tsx:275
msgid "List Name" msgid "List Name"
@@ -2045,7 +2045,7 @@ msgstr "取消屏蔽列表"
#: src/view/screens/ProfileList.tsx:301 #: src/view/screens/ProfileList.tsx:301
msgid "List unmuted" msgid "List unmuted"
msgstr "取消静音列表" msgstr "取消隐藏列表"
#: src/Navigation.tsx:110 #: src/Navigation.tsx:110
#: src/view/screens/Profile.tsx:176 #: src/view/screens/Profile.tsx:176
@@ -2092,7 +2092,7 @@ msgstr "登出"
#: src/view/screens/Moderation.tsx:136 #: src/view/screens/Moderation.tsx:136
msgid "Logged-out visibility" msgid "Logged-out visibility"
msgstr "登出可见性" msgstr "未登录用户可见性"
#: src/view/com/auth/login/ChooseAccountForm.tsx:133 #: src/view/com/auth/login/ChooseAccountForm.tsx:133
msgid "Login to account that is not listed" msgid "Login to account that is not listed"
@@ -2130,47 +2130,47 @@ msgstr "来自服务器的信息:{0}"
#: src/view/shell/Drawer.tsx:514 #: src/view/shell/Drawer.tsx:514
#: src/view/shell/Drawer.tsx:515 #: src/view/shell/Drawer.tsx:515
msgid "Moderation" msgid "Moderation"
msgstr "管理员" msgstr "限制"
#: src/view/com/lists/ListCard.tsx:92 #: src/view/com/lists/ListCard.tsx:92
#: src/view/com/modals/UserAddRemoveLists.tsx:206 #: src/view/com/modals/UserAddRemoveLists.tsx:206
msgid "Moderation list by {0}" msgid "Moderation list by {0}"
msgstr "管理员列表由 {0} 创建" msgstr "限制列表由 {0} 创建"
#: src/view/screens/ProfileList.tsx:774 #: src/view/screens/ProfileList.tsx:774
msgid "Moderation list by <0/>" msgid "Moderation list by <0/>"
msgstr "管理员列表由 </0> 创建" msgstr "限制列表由 </0> 创建"
#: src/view/com/lists/ListCard.tsx:90 #: src/view/com/lists/ListCard.tsx:90
#: src/view/com/modals/UserAddRemoveLists.tsx:204 #: src/view/com/modals/UserAddRemoveLists.tsx:204
#: src/view/screens/ProfileList.tsx:772 #: src/view/screens/ProfileList.tsx:772
msgid "Moderation list by you" msgid "Moderation list by you"
msgstr "管理员列表由你创建" msgstr "限制列表由你创建"
#: src/view/com/modals/CreateOrEditList.tsx:197 #: src/view/com/modals/CreateOrEditList.tsx:197
msgid "Moderation list created" msgid "Moderation list created"
msgstr "管理员列表已创建" msgstr "限制列表已创建"
#: src/view/com/modals/CreateOrEditList.tsx:183 #: src/view/com/modals/CreateOrEditList.tsx:183
msgid "Moderation list updated" msgid "Moderation list updated"
msgstr "管理员列表已更新" msgstr "限制列表已更新"
#: src/view/screens/Moderation.tsx:95 #: src/view/screens/Moderation.tsx:95
msgid "Moderation lists" msgid "Moderation lists"
msgstr "管理员列表" msgstr "限制列表"
#: src/Navigation.tsx:120 #: src/Navigation.tsx:120
#: src/view/screens/ModerationModlists.tsx:58 #: src/view/screens/ModerationModlists.tsx:58
msgid "Moderation Lists" msgid "Moderation Lists"
msgstr "管理员列表" msgstr "限制列表"
#: src/view/screens/Settings.tsx:613 #: src/view/screens/Settings.tsx:613
msgid "Moderation settings" msgid "Moderation settings"
msgstr "管理员设置" msgstr "限制设置"
#: src/view/com/modals/ModerationDetails.tsx:35 #: src/view/com/modals/ModerationDetails.tsx:35
msgid "Moderator has chosen to set a general warning on the content." msgid "Moderator has chosen to set a general warning on the content."
msgstr "管理员选择对内容设置一般警告。" msgstr "限制选择对内容设置一般警告。"
#: src/view/shell/desktop/Feeds.tsx:53 #: src/view/shell/desktop/Feeds.tsx:53
msgid "More feeds" msgid "More feeds"
@@ -2192,48 +2192,48 @@ msgstr "最多点赞优先"
#: src/view/com/profile/ProfileHeader.tsx:374 #: src/view/com/profile/ProfileHeader.tsx:374
msgid "Mute Account" msgid "Mute Account"
msgstr "静音账户" msgstr "隐藏账户"
#: src/view/screens/ProfileList.tsx:543 #: src/view/screens/ProfileList.tsx:543
msgid "Mute accounts" msgid "Mute accounts"
msgstr "静音账户" msgstr "隐藏账户"
#: src/view/screens/ProfileList.tsx:490 #: src/view/screens/ProfileList.tsx:490
msgid "Mute list" msgid "Mute list"
msgstr "静音列表" msgstr "隐藏列表"
#: src/view/screens/ProfileList.tsx:274 #: src/view/screens/ProfileList.tsx:274
msgid "Mute these accounts?" msgid "Mute these accounts?"
msgstr "静音这些账户?" msgstr "隐藏这些账户?"
#: src/view/screens/ProfileList.tsx:278 #: src/view/screens/ProfileList.tsx:278
msgid "Mute this List" msgid "Mute this List"
msgstr "静音这个列表" msgstr "隐藏这个列表"
#: src/view/com/util/forms/PostDropdownBtn.tsx:171 #: src/view/com/util/forms/PostDropdownBtn.tsx:171
msgid "Mute thread" msgid "Mute thread"
msgstr "静音讨论串" msgstr "隐藏讨论串"
#: src/view/com/lists/ListCard.tsx:101 #: src/view/com/lists/ListCard.tsx:101
msgid "Muted" msgid "Muted"
msgstr "已经印" msgstr "已隐藏"
#: src/view/screens/Moderation.tsx:109 #: src/view/screens/Moderation.tsx:109
msgid "Muted accounts" msgid "Muted accounts"
msgstr "已静音账户" msgstr "已隐藏账户"
#: src/Navigation.tsx:125 #: src/Navigation.tsx:125
#: src/view/screens/ModerationMutedAccounts.tsx:107 #: src/view/screens/ModerationMutedAccounts.tsx:107
msgid "Muted Accounts" msgid "Muted Accounts"
msgstr "已静音账户" msgstr "已隐藏账户"
#: src/view/screens/ModerationMutedAccounts.tsx:115 #: src/view/screens/ModerationMutedAccounts.tsx:115
msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private."
msgstr "已静音的账户将不会在你的通知或时间线中显示,被静音账户将不会收到通知。" msgstr "已隐藏的账户将不会在你的通知或时间线中显示,被隐藏账户将不会收到通知。"
#: src/view/screens/ProfileList.tsx:276 #: src/view/screens/ProfileList.tsx:276
msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them."
msgstr "被静音的账户将不会得知你已将他静音,已静音的账户将不会在你的通知或时间线中显示。" msgstr "被隐藏的账户将不会得知你已将他隐藏,已隐藏的账户将不会在你的通知或时间线中显示。"
#: src/view/com/modals/BirthDateSettings.tsx:56 #: src/view/com/modals/BirthDateSettings.tsx:56
msgid "My Birthday" msgid "My Birthday"
@@ -2293,15 +2293,15 @@ msgstr "永远不会失去对你的关注者或数据的访问。"
#: src/view/screens/Lists.tsx:76 #: src/view/screens/Lists.tsx:76
msgctxt "action" msgctxt "action"
msgid "New" msgid "New"
msgstr "新" msgstr "新"
#: src/view/screens/ModerationModlists.tsx:78 #: src/view/screens/ModerationModlists.tsx:78
msgid "New" msgid "New"
msgstr "新" msgstr "新"
#: src/view/com/modals/CreateOrEditList.tsx:252 #: src/view/com/modals/CreateOrEditList.tsx:252
msgid "New Moderation List" msgid "New Moderation List"
msgstr "新的管理员列表" msgstr "新的限制列表"
#: src/view/com/auth/login/SetNewPasswordForm.tsx:150 #: src/view/com/auth/login/SetNewPasswordForm.tsx:150
msgid "New password" msgid "New password"
@@ -2309,7 +2309,7 @@ msgstr "新密码"
#: src/view/com/modals/ChangePassword.tsx:215 #: src/view/com/modals/ChangePassword.tsx:215
msgid "New Password" msgid "New Password"
msgstr "" msgstr "新密码"
#: src/view/com/feeds/FeedPage.tsx:201 #: src/view/com/feeds/FeedPage.tsx:201
msgctxt "action" msgctxt "action"
@@ -2370,7 +2370,7 @@ msgstr "下一张图片"
#: src/view/screens/PreferencesThreads.tsx:106 #: src/view/screens/PreferencesThreads.tsx:106
#: src/view/screens/PreferencesThreads.tsx:129 #: src/view/screens/PreferencesThreads.tsx:129
msgid "No" msgid "No"
msgstr "没有" msgstr "停用"
#: src/view/screens/ProfileFeed.tsx:584 #: src/view/screens/ProfileFeed.tsx:584
#: src/view/screens/ProfileList.tsx:754 #: src/view/screens/ProfileList.tsx:754
@@ -2398,7 +2398,7 @@ msgstr "未找到\"{query}\"的结果"
#: src/view/screens/Search/Search.tsx:280 #: src/view/screens/Search/Search.tsx:280
#: src/view/screens/Search/Search.tsx:308 #: src/view/screens/Search/Search.tsx:308
msgid "No results found for {query}" msgid "No results found for {query}"
msgstr "未找到{query}的结果" msgstr "未找到 {query} 的结果"
#: src/view/com/modals/EmbedConsent.tsx:129 #: src/view/com/modals/EmbedConsent.tsx:129
msgid "No thanks" msgid "No thanks"
@@ -2480,7 +2480,7 @@ msgstr "打开"
#: src/view/com/composer/Composer.tsx:470 #: src/view/com/composer/Composer.tsx:470
#: src/view/com/composer/Composer.tsx:471 #: src/view/com/composer/Composer.tsx:471
msgid "Open emoji picker" msgid "Open emoji picker"
msgstr "打开emoji选择器" msgstr "打开 emoji 选择器"
#: src/view/screens/Settings.tsx:706 #: src/view/screens/Settings.tsx:706
msgid "Open links with in-app browser" msgid "Open links with in-app browser"
@@ -2558,7 +2558,7 @@ msgstr "打开使用自定义域名的模式"
#: src/view/screens/Settings.tsx:614 #: src/view/screens/Settings.tsx:614
msgid "Opens moderation settings" msgid "Opens moderation settings"
msgstr "打开管理员设置" msgstr "打开限制设置"
#: src/view/com/auth/login/LoginForm.tsx:236 #: src/view/com/auth/login/LoginForm.tsx:236
msgid "Opens password reset form" msgid "Opens password reset form"
@@ -2688,11 +2688,11 @@ msgstr "播放 GIF"
#: src/view/com/auth/create/state.ts:177 #: src/view/com/auth/create/state.ts:177
msgid "Please choose your handle." msgid "Please choose your handle."
msgstr "请选择你的昵称。" msgstr "请设置你的用户识别符。"
#: src/view/com/auth/create/state.ts:160 #: src/view/com/auth/create/state.ts:160
msgid "Please choose your password." msgid "Please choose your password."
msgstr "请选择你的密码。" msgstr "请设置你的密码。"
#: src/view/com/modals/ChangeEmail.tsx:67 #: src/view/com/modals/ChangeEmail.tsx:67
msgid "Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed." msgid "Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed."
@@ -2850,7 +2850,7 @@ msgstr "公开内容"
#: src/view/screens/ModerationModlists.tsx:61 #: src/view/screens/ModerationModlists.tsx:61
msgid "Public, shareable lists of users to mute or block in bulk." msgid "Public, shareable lists of users to mute or block in bulk."
msgstr "公开且可共享的批量静音或屏蔽列表。" msgstr "公开且可共享的批量隐藏或屏蔽列表。"
#: src/view/screens/Lists.tsx:61 #: src/view/screens/Lists.tsx:61
msgid "Public, shareable lists which can drive feeds." msgid "Public, shareable lists which can drive feeds."
@@ -3050,7 +3050,7 @@ msgstr "请求码"
#: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:239
#: src/view/com/modals/ChangePassword.tsx:241 #: src/view/com/modals/ChangePassword.tsx:241
msgid "Request Code" msgid "Request Code"
msgstr "" msgstr "确认码"
#: src/view/screens/Settings.tsx:450 #: src/view/screens/Settings.tsx:450
msgid "Require alt text before posting" msgid "Require alt text before posting"
@@ -3067,7 +3067,7 @@ msgstr "重置码"
#: src/view/com/modals/ChangePassword.tsx:190 #: src/view/com/modals/ChangePassword.tsx:190
msgid "Reset Code" msgid "Reset Code"
msgstr "" msgstr "重置代码"
#: src/view/screens/Settings.tsx:806 #: src/view/screens/Settings.tsx:806
msgid "Reset onboarding" msgid "Reset onboarding"
@@ -3155,7 +3155,7 @@ msgstr "保存更改"
#: src/view/com/modals/ChangeHandle.tsx:170 #: src/view/com/modals/ChangeHandle.tsx:170
msgid "Save handle change" msgid "Save handle change"
msgstr "保存新的昵称" msgstr "保存新的用户识别符"
#: src/view/com/modals/crop-image/CropImage.web.tsx:144 #: src/view/com/modals/crop-image/CropImage.web.tsx:144
msgid "Save image crop" msgid "Save image crop"
@@ -3171,7 +3171,7 @@ msgstr "保存个人资料中所做的变更"
#: src/view/com/modals/ChangeHandle.tsx:171 #: src/view/com/modals/ChangeHandle.tsx:171
msgid "Saves handle change to {handle}" msgid "Saves handle change to {handle}"
msgstr "保存昵称更改至 {handle}" msgstr "保存用户识别符更改至 {handle}"
#: src/screens/Onboarding/index.tsx:36 #: src/screens/Onboarding/index.tsx:36
msgid "Science" msgid "Science"
@@ -3321,7 +3321,7 @@ msgstr "设置年龄"
#: src/view/screens/Settings.tsx:482 #: src/view/screens/Settings.tsx:482
msgid "Set color theme to dark" msgid "Set color theme to dark"
msgstr "设置主题为色模式" msgstr "设置主题为色模式"
#: src/view/screens/Settings.tsx:475 #: src/view/screens/Settings.tsx:475
msgid "Set color theme to light" msgid "Set color theme to light"
@@ -3333,11 +3333,11 @@ msgstr "设置主题跟随系统设置"
#: src/view/screens/Settings.tsx:508 #: src/view/screens/Settings.tsx:508
msgid "Set dark theme to the dark theme" msgid "Set dark theme to the dark theme"
msgstr "" msgstr "设置深色模式至深黑"
#: src/view/screens/Settings.tsx:501 #: src/view/screens/Settings.tsx:501
msgid "Set dark theme to the dim theme" msgid "Set dark theme to the dim theme"
msgstr "" msgstr "设置深色模式至暗淡"
#: src/view/com/auth/login/SetNewPasswordForm.tsx:104 #: src/view/com/auth/login/SetNewPasswordForm.tsx:104
msgid "Set new password" msgid "Set new password"
@@ -3349,23 +3349,23 @@ msgstr "设置密码"
#: src/view/screens/PreferencesHomeFeed.tsx:225 #: src/view/screens/PreferencesHomeFeed.tsx:225
msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible."
msgstr "此设置项设为\"否\"以隐藏来自订阅信息流的所有引用帖子,转发仍将可见。" msgstr "停用此设置项以隐藏来自订阅信息流的所有引用帖子,转发仍将可见。"
#: src/view/screens/PreferencesHomeFeed.tsx:122 #: src/view/screens/PreferencesHomeFeed.tsx:122
msgid "Set this setting to \"No\" to hide all replies from your feed." msgid "Set this setting to \"No\" to hide all replies from your feed."
msgstr "此设置项设为\"否\"以隐藏来自订阅信息流的所有回复。" msgstr "停用此设置项以隐藏来自订阅信息流的所有回复。"
#: src/view/screens/PreferencesHomeFeed.tsx:191 #: src/view/screens/PreferencesHomeFeed.tsx:191
msgid "Set this setting to \"No\" to hide all reposts from your feed." msgid "Set this setting to \"No\" to hide all reposts from your feed."
msgstr "此设置项设为\"否\"以隐藏来自订阅信息流的所有转发。" msgstr "停用此设置项以隐藏来自订阅信息流的所有转发。"
#: src/view/screens/PreferencesThreads.tsx:122 #: src/view/screens/PreferencesThreads.tsx:122
msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature." msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature."
msgstr "此设置项设为\"是\"以在分层视图中显示回复。这是一个实验性功能。" msgstr "启用此设置项以在分层视图中显示回复。这是一个实验性功能。"
#: src/view/screens/PreferencesHomeFeed.tsx:261 #: src/view/screens/PreferencesHomeFeed.tsx:261
msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature." msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature."
msgstr "此设置项设为\"是\"以在关注信息流中显示已保存信息流的样例。这是一个实验性功能。" msgstr "启用此设置项以在关注信息流中显示已保存信息流的样例。这是一个实验性功能。"
#: src/screens/Onboarding/Layout.tsx:50 #: src/screens/Onboarding/Layout.tsx:50
msgid "Set up your account" msgid "Set up your account"
@@ -3846,7 +3846,7 @@ msgstr "出现问题了,请检查你的互联网连接并重试。"
#: src/view/com/util/ErrorBoundary.tsx:36 #: src/view/com/util/ErrorBoundary.tsx:36
msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" msgid "There was an unexpected issue in the application. Please let us know if this happened to you!"
msgstr "\"应用发生意外错误,请联系我们进行错误反馈!" msgstr "应用发生意外错误,请联系我们进行错误反馈!"
#: src/screens/Deactivated.tsx:107 #: src/screens/Deactivated.tsx:107
msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can."
@@ -3862,7 +3862,7 @@ msgstr "这里是一些受欢迎的账号,你可能会喜欢:"
#: src/view/com/util/moderation/ScreenHider.tsx:88 #: src/view/com/util/moderation/ScreenHider.tsx:88
msgid "This {screenDescription} has been flagged:" msgid "This {screenDescription} has been flagged:"
msgstr "{screenDescription}\" 已被标记:" msgstr "这个 {screenDescription} 已被标记:"
#: src/view/com/util/moderation/ScreenHider.tsx:83 #: src/view/com/util/moderation/ScreenHider.tsx:83
msgid "This account has requested that users sign in to view their profile." msgid "This account has requested that users sign in to view their profile."
@@ -3928,11 +3928,11 @@ msgstr "此用户包含在你已屏蔽的 <0/> 列表中。"
#: src/view/com/modals/ModerationDetails.tsx:74 #: src/view/com/modals/ModerationDetails.tsx:74
msgid "This user is included in the <0/> list which you have muted." msgid "This user is included in the <0/> list which you have muted."
msgstr "" msgstr "此用户包含在你已隐藏的 <0/> 列表中。"
#: src/view/com/modals/ModerationDetails.tsx:74 #: src/view/com/modals/ModerationDetails.tsx:74
#~ msgid "This user is included the <0/> list which you have muted." #~ msgid "This user is included the <0/> list which you have muted."
#~ msgstr "此用户包含在你已静音的 <0/> 列表中。" #~ msgstr "此用户包含在你已隐藏的 <0/> 列表中。"
#: src/view/com/modals/SelfLabel.tsx:137 #: src/view/com/modals/SelfLabel.tsx:137
msgid "This warning is only available for posts with media attached." msgid "This warning is only available for posts with media attached."
@@ -3980,7 +3980,7 @@ msgstr "取消屏蔽列表"
#: src/view/screens/ProfileList.tsx:490 #: src/view/screens/ProfileList.tsx:490
msgid "Un-mute list" msgid "Un-mute list"
msgstr "取消静音列表" msgstr "取消隐藏列表"
#: src/view/com/auth/create/CreateAccount.tsx:66 #: src/view/com/auth/create/CreateAccount.tsx:66
#: src/view/com/auth/login/ForgotPasswordForm.tsx:87 #: src/view/com/auth/login/ForgotPasswordForm.tsx:87
@@ -4031,15 +4031,15 @@ msgstr "取消喜欢"
#: src/view/screens/ProfileList.tsx:596 #: src/view/screens/ProfileList.tsx:596
msgid "Unmute" msgid "Unmute"
msgstr "取消静音" msgstr "取消隐藏"
#: src/view/com/profile/ProfileHeader.tsx:373 #: src/view/com/profile/ProfileHeader.tsx:373
msgid "Unmute Account" msgid "Unmute Account"
msgstr "取消静音账户" msgstr "取消隐藏账户"
#: src/view/com/util/forms/PostDropdownBtn.tsx:171 #: src/view/com/util/forms/PostDropdownBtn.tsx:171
msgid "Unmute thread" msgid "Unmute thread"
msgstr "取消静音讨论串" msgstr "取消隐藏讨论串"
#: src/view/screens/ProfileFeed.tsx:353 #: src/view/screens/ProfileFeed.tsx:353
#: src/view/screens/ProfileList.tsx:580 #: src/view/screens/ProfileList.tsx:580
@@ -4048,7 +4048,7 @@ msgstr "取消固定"
#: src/view/screens/ProfileList.tsx:473 #: src/view/screens/ProfileList.tsx:473
msgid "Unpin moderation list" msgid "Unpin moderation list"
msgstr "取消固定管理员列表" msgstr "取消固定限制列表"
#: src/view/screens/ProfileFeed.tsx:345 #: src/view/screens/ProfileFeed.tsx:345
msgid "Unsave" msgid "Unsave"
@@ -4090,11 +4090,11 @@ msgstr "使用系统默认浏览器"
#: src/view/com/modals/AddAppPasswords.tsx:155 #: src/view/com/modals/AddAppPasswords.tsx:155
msgid "Use this to sign into the other app along with your handle." msgid "Use this to sign into the other app along with your handle."
msgstr "使用这个和你的昵称一起登录其他应用。" msgstr "使用这个和你的用户识别符一起登录其他应用。"
#: src/view/com/modals/ServerInput.tsx:105 #: src/view/com/modals/ServerInput.tsx:105
msgid "Use your domain as your Bluesky client service provider" msgid "Use your domain as your Bluesky client service provider"
msgstr "使用你的域名作为 Bluesky 客户服务提供" msgstr "使用你的域名作为 Bluesky 客户端的服务提供"
#: src/view/com/modals/InviteCodes.tsx:200 #: src/view/com/modals/InviteCodes.tsx:200
msgid "Used by:" msgid "Used by:"
@@ -4114,7 +4114,7 @@ msgstr "用户屏蔽了你"
#: src/view/com/auth/create/Step3.tsx:41 #: src/view/com/auth/create/Step3.tsx:41
msgid "User handle" msgid "User handle"
msgstr "用户昵称" msgstr "用户识别符"
#: src/view/com/lists/ListCard.tsx:84 #: src/view/com/lists/ListCard.tsx:84
#: src/view/com/modals/UserAddRemoveLists.tsx:198 #: src/view/com/modals/UserAddRemoveLists.tsx:198
@@ -4220,7 +4220,7 @@ msgstr "警告"
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124
msgid "We also think you'll like \"For You\" by Skygaze:" msgid "We also think you'll like \"For You\" by Skygaze:"
msgstr "我们认为还你会喜欢 Skygaze 所维护的 \"For You\"" msgstr "我们认为还你会喜欢 Skygaze 所维护的 \"For You\":"
#: src/screens/Deactivated.tsx:134 #: src/screens/Deactivated.tsx:134
msgid "We estimate {estimatedTime} until your account is ready." msgid "We estimate {estimatedTime} until your account is ready."
@@ -4236,7 +4236,7 @@ msgstr "我们已经看完了你关注的帖子。这是来自 <0/> 的最新消
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:119 #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:119
msgid "We recommend our \"Discover\" feed:" msgid "We recommend our \"Discover\" feed:"
msgstr "我们推荐我们的 \"Discover\" 信息流" msgstr "我们推荐我们的 \"Discover\" 信息流:"
#: src/screens/Onboarding/StepInterests/index.tsx:133 #: src/screens/Onboarding/StepInterests/index.tsx:133
msgid "We weren't able to connect. Please try again to continue setting up your account. If it continues to fail, you can skip this flow." msgid "We weren't able to connect. Please try again to continue setting up your account. If it continues to fail, you can skip this flow."
@@ -4329,7 +4329,7 @@ msgstr "XXXXXX"
#: src/view/screens/PreferencesThreads.tsx:106 #: src/view/screens/PreferencesThreads.tsx:106
#: src/view/screens/PreferencesThreads.tsx:129 #: src/view/screens/PreferencesThreads.tsx:129
msgid "Yes" msgid "Yes"
msgstr "是的" msgstr "启用"
#: src/screens/Deactivated.tsx:131 #: src/screens/Deactivated.tsx:131
msgid "You are in line." msgid "You are in line."
@@ -4378,11 +4378,11 @@ msgstr "你已屏蔽了此用户,你将无法查看他们发布的内容。"
#: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:87
#: src/view/com/modals/ChangePassword.tsx:121 #: src/view/com/modals/ChangePassword.tsx:121
msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." msgid "You have entered an invalid code. It should look like XXXXX-XXXXX."
msgstr "" msgstr "你输入的邀请码无效。它应该长得像这样 XXXXX-XXXXX。"
#: src/view/com/modals/ModerationDetails.tsx:87 #: src/view/com/modals/ModerationDetails.tsx:87
msgid "You have muted this user." msgid "You have muted this user."
msgstr "你已静音这个用户。" msgstr "你已隐藏这个用户。"
#: src/view/com/feeds/ProfileFeedgens.tsx:136 #: src/view/com/feeds/ProfileFeedgens.tsx:136
msgid "You have no feeds." msgid "You have no feeds."
@@ -4403,7 +4403,7 @@ msgstr "你尚未创建任何 App 专用密码,可以通过点击下面的按
#: src/view/screens/ModerationMutedAccounts.tsx:131 #: src/view/screens/ModerationMutedAccounts.tsx:131
msgid "You have not muted any accounts yet. To mute an account, go to their profile and selected \"Mute account\" from the menu on their account." msgid "You have not muted any accounts yet. To mute an account, go to their profile and selected \"Mute account\" from the menu on their account."
msgstr "你还没有静音任何账号。要静音账号,请转到其个人资料并在其账号上的菜单中选择 \"静音账号\"。" msgstr "你还没有隐藏任何账号。要隐藏账号,请转到其个人资料并在其账号上的菜单中选择 \"隐藏账号\"。"
#: src/view/com/modals/ContentFilteringSettings.tsx:170 #: src/view/com/modals/ContentFilteringSettings.tsx:170
msgid "You must be 18 or older to enable adult content." msgid "You must be 18 or older to enable adult content."
@@ -4487,11 +4487,11 @@ msgstr "你的关注信息流为空!关注更多用户去看看他们发了什
#: src/view/com/auth/create/Step3.tsx:45 #: src/view/com/auth/create/Step3.tsx:45
msgid "Your full handle will be" msgid "Your full handle will be"
msgstr "你的完整昵称将修改为" msgstr "你的完整用户识别符将修改为"
#: src/view/com/modals/ChangeHandle.tsx:270 #: src/view/com/modals/ChangeHandle.tsx:270
msgid "Your full handle will be <0>@{0}</0>" msgid "Your full handle will be <0>@{0}</0>"
msgstr "你的完整昵称将修改为 <0>@{0}</0>" msgstr "你的完整用户识别符将修改为 <0>@{0}</0>"
#: src/view/screens/Settings.tsx:430 #: src/view/screens/Settings.tsx:430
#: src/view/shell/desktop/RightNav.tsx:137 #: src/view/shell/desktop/RightNav.tsx:137
@@ -4501,7 +4501,7 @@ msgstr "在使用 App 专用密码登录时,你的邀请码将被隐藏"
#: src/view/com/modals/ChangePassword.tsx:155 #: src/view/com/modals/ChangePassword.tsx:155
msgid "Your password has been changed successfully!" msgid "Your password has been changed successfully!"
msgstr "" msgstr "你的密码已更改成功!"
#: src/view/com/composer/Composer.tsx:267 #: src/view/com/composer/Composer.tsx:267
msgid "Your post has been published" msgid "Your post has been published"
@@ -4511,7 +4511,7 @@ msgstr "你的帖子已发送"
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:59 #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:59
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:59 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:59
msgid "Your posts, likes, and blocks are public. Mutes are private." msgid "Your posts, likes, and blocks are public. Mutes are private."
msgstr "你的帖子、点赞和屏蔽是公开可见的,而静音不可见。" msgstr "你的帖子、点赞和屏蔽是公开可见的,而隐藏不可见。"
#: src/view/com/modals/SwitchAccount.tsx:84 #: src/view/com/modals/SwitchAccount.tsx:84
#: src/view/screens/Settings.tsx:125 #: src/view/screens/Settings.tsx:125
@@ -4524,4 +4524,4 @@ msgstr "你的回复已发送"
#: src/view/com/auth/create/Step3.tsx:28 #: src/view/com/auth/create/Step3.tsx:28
msgid "Your user handle" msgid "Your user handle"
msgstr "你的用户昵称" msgstr "你的用户识别符"
+4 -5
View File
@@ -10,9 +10,8 @@ import {pluralize} from '#/lib/strings/helpers'
import {atoms as a, useTheme, useBreakpoints} from '#/alf' import {atoms as a, useTheme, useBreakpoints} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Text} from '#/components/Typography' import {Text, P} from '#/components/Typography'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {H2, P} from '#/components/Typography'
import {ScrollView} from '#/view/com/util/Views' import {ScrollView} from '#/view/com/util/Views'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Logo} from '#/view/icons/Logo' import {Logo} from '#/view/icons/Logo'
@@ -100,10 +99,10 @@ export function Deactivated() {
<Logo width={120} /> <Logo width={120} />
</View> </View>
<H2 style={[a.pb_sm]}> <Text style={[a.text_4xl, a.font_bold, a.pb_sm]}>
<Trans>You're in line</Trans> <Trans>You're in line</Trans>
</H2> </Text>
<P style={[t.atoms.text_contrast_700]}> <P style={[t.atoms.text_contrast_medium]}>
<Trans> <Trans>
There's been a rush of new users to Bluesky! We'll activate your There's been a rush of new users to Bluesky! We'll activate your
account as soon as we can. account as soon as we can.
+4 -2
View File
@@ -165,7 +165,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
isWeb ? a.fixed : a.absolute, isWeb ? a.fixed : a.absolute,
{bottom: 0, left: 0, right: 0}, {bottom: 0, left: 0, right: 0},
t.atoms.bg, t.atoms.bg,
t.atoms.border, t.atoms.border_contrast_low,
a.border_t, a.border_t,
a.align_center, a.align_center,
gtMobile ? a.px_5xl : a.px_xl, gtMobile ? a.px_5xl : a.px_xl,
@@ -227,5 +227,7 @@ export function Description({
style, style,
}: React.PropsWithChildren<TextStyleProp>) { }: React.PropsWithChildren<TextStyleProp>) {
const t = useTheme() const t = useTheme()
return <P style={[t.atoms.text_contrast_700, flatten(style)]}>{children}</P> return (
<P style={[t.atoms.text_contrast_medium, flatten(style)]}>{children}</P>
)
} }
@@ -8,7 +8,7 @@ import {msg} from '@lingui/macro'
import {useTheme, atoms as a} from '#/alf' import {useTheme, atoms as a} from '#/alf'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed' import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed'
import {Text, H3} from '#/components/Typography' import {Text} from '#/components/Typography'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
@@ -94,14 +94,14 @@ function PrimaryFeedCardInner({
</View> </View>
<View style={[a.pt_xs, a.flex_grow]}> <View style={[a.pt_xs, a.flex_grow]}>
<H3 <Text
style={[ style={[
a.text_lg, a.text_lg,
a.font_bold, a.font_bold,
ctx.selected && styles.textSelected, ctx.selected && styles.textSelected,
]}> ]}>
{feed.displayName} {feed.displayName}
</H3> </Text>
<Text <Text
style={[ style={[
@@ -137,7 +137,7 @@ function PrimaryFeedCardInner({
}, },
a.mt_md, a.mt_md,
a.w_full, a.w_full,
t.name === 'light' ? t.atoms.border : t.atoms.border_contrast, t.atoms.border_contrast_low,
ctx.selected && { ctx.selected && {
borderTopColor: t.palette.white, borderTopColor: t.palette.white,
}, },
@@ -239,14 +239,14 @@ function FeedCardInner({feed}: {feed: FeedSourceInfo; config: FeedConfig}) {
</View> </View>
<View style={[a.pt_2xs, a.flex_grow]}> <View style={[a.pt_2xs, a.flex_grow]}>
<H3 <Text
style={[ style={[
a.text_md, a.text_md,
a.font_bold, a.font_bold,
ctx.selected && styles.textSelected, ctx.selected && styles.textSelected,
]}> ]}>
{feed.displayName} {feed.displayName}
</H3> </Text>
<Text <Text
style={[ style={[
{opacity: 0.8}, {opacity: 0.8},
@@ -281,7 +281,7 @@ function FeedCardInner({feed}: {feed: FeedSourceInfo; config: FeedConfig}) {
}, },
a.mt_md, a.mt_md,
a.w_full, a.w_full,
t.name === 'light' ? t.atoms.border : t.atoms.border_contrast, t.atoms.border_contrast_low,
ctx.selected && { ctx.selected && {
borderTopColor: t.palette.primary_200, borderTopColor: t.palette.primary_200,
}, },
@@ -360,7 +360,7 @@ export function FeedCardPlaceholder({primary}: {primary?: boolean}) {
}, },
a.mt_md, a.mt_md,
a.w_full, a.w_full,
t.atoms.border, t.atoms.border_contrast_low,
]} ]}
/> />
+18 -3
View File
@@ -115,12 +115,22 @@ export function StepAlgoFeeds() {
onChange={setPrimaryFeedUris} onChange={setPrimaryFeedUris}
label={_(msg`Select your primary algorithmic feeds`)}> label={_(msg`Select your primary algorithmic feeds`)}>
<Text <Text
style={[a.text_md, a.pt_4xl, a.pb_md, t.atoms.text_contrast_700]}> style={[
a.text_md,
a.pt_4xl,
a.pb_md,
t.atoms.text_contrast_medium,
]}>
<Trans>We recommend our "Discover" feed:</Trans> <Trans>We recommend our "Discover" feed:</Trans>
</Text> </Text>
<FeedCard config={PRIMARY_FEEDS[0]} /> <FeedCard config={PRIMARY_FEEDS[0]} />
<Text <Text
style={[a.text_md, a.pt_4xl, a.pb_lg, t.atoms.text_contrast_700]}> style={[
a.text_md,
a.pt_4xl,
a.pb_lg,
t.atoms.text_contrast_medium,
]}>
<Trans>We also think you'll like "For You" by Skygaze:</Trans> <Trans>We also think you'll like "For You" by Skygaze:</Trans>
</Text> </Text>
<FeedCard config={PRIMARY_FEEDS[1]} /> <FeedCard config={PRIMARY_FEEDS[1]} />
@@ -131,7 +141,12 @@ export function StepAlgoFeeds() {
onChange={setSeconaryFeedUris} onChange={setSeconaryFeedUris}
label={_(msg`Select your secondary algorithmic feeds`)}> label={_(msg`Select your secondary algorithmic feeds`)}>
<Text <Text
style={[a.text_md, a.pt_4xl, a.pb_lg, t.atoms.text_contrast_700]}> style={[
a.text_md,
a.pt_4xl,
a.pb_lg,
t.atoms.text_contrast_medium,
]}>
<Trans>There are many feeds to try:</Trans> <Trans>There are many feeds to try:</Trans>
</Text> </Text>
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
+3 -3
View File
@@ -101,7 +101,7 @@ export function StepFinished() {
<Trans>Public</Trans> <Trans>Public</Trans>
</Text> </Text>
<Text <Text
style={[t.atoms.text_contrast_500, a.text_md, a.leading_snug]}> style={[t.atoms.text_contrast_medium, a.text_md, a.leading_snug]}>
<Trans> <Trans>
Your posts, likes, and blocks are public. Mutes are private. Your posts, likes, and blocks are public. Mutes are private.
</Trans> </Trans>
@@ -115,7 +115,7 @@ export function StepFinished() {
<Trans>Open</Trans> <Trans>Open</Trans>
</Text> </Text>
<Text <Text
style={[t.atoms.text_contrast_500, a.text_md, a.leading_snug]}> style={[t.atoms.text_contrast_medium, a.text_md, a.leading_snug]}>
<Trans>Never lose access to your followers or data.</Trans> <Trans>Never lose access to your followers or data.</Trans>
</Text> </Text>
</View> </View>
@@ -131,7 +131,7 @@ export function StepFinished() {
<Trans>Flexible</Trans> <Trans>Flexible</Trans>
</Text> </Text>
<Text <Text
style={[t.atoms.text_contrast_500, a.text_md, a.leading_snug]}> style={[t.atoms.text_contrast_medium, a.text_md, a.leading_snug]}>
<Trans>Choose the algorithms that power your custom feeds.</Trans> <Trans>Choose the algorithms that power your custom feeds.</Trans>
</Text> </Text>
</View> </View>
@@ -96,7 +96,7 @@ export function AdultContentEnabledPref({
<Text <Text
style={[ style={[
a.flex_1, a.flex_1,
t.atoms.text_contrast_700, t.atoms.text_contrast_medium,
a.leading_snug, a.leading_snug,
{paddingTop: 1}, {paddingTop: 1},
]}> ]}>
@@ -57,7 +57,7 @@ export function ModerationOption({
entering={isMounted.current ? FadeIn : undefined}> entering={isMounted.current ? FadeIn : undefined}>
<View style={[a.gap_xs, {width: '50%'}]}> <View style={[a.gap_xs, {width: '50%'}]}>
<Text style={[a.font_bold]}>{groupInfo.title}</Text> <Text style={[a.font_bold]}>{groupInfo.title}</Text>
<Text style={[t.atoms.text_contrast_700, a.leading_snug]}> <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
{groupInfo.subtitle} {groupInfo.subtitle}
</Text> </Text>
</View> </View>
@@ -95,7 +95,7 @@ export function SuggestedAccountCard({
<Text style={[a.font_bold, a.text_md, a.pb_xs]} numberOfLines={1}> <Text style={[a.font_bold, a.text_md, a.pb_xs]} numberOfLines={1}>
{profile.displayName} {profile.displayName}
</Text> </Text>
<Text style={[t.atoms.text_contrast_600]}>{profile.handle}</Text> <Text style={[t.atoms.text_contrast_medium]}>{profile.handle}</Text>
</View> </View>
</View> </View>
@@ -124,7 +124,7 @@ export function SuggestedAccountCard({
borderTopWidth: 1, borderTopWidth: 1,
}, },
a.w_full, a.w_full,
t.name === 'light' ? t.atoms.border : t.atoms.border_contrast, t.atoms.border_contrast_low,
ctx.selected && { ctx.selected && {
borderTopColor: t.palette.primary_200, borderTopColor: t.palette.primary_200,
}, },
@@ -175,7 +175,11 @@ export function StepSuggestedAccounts() {
)} )}
onPress={handleContinue}> onPress={handleContinue}>
<ButtonText> <ButtonText>
<Trans>Follow All</Trans> {dids.length === 20 ? (
<Trans>Follow All</Trans>
) : (
<Trans>Follow</Trans>
)}
</ButtonText> </ButtonText>
<ButtonIcon icon={saving ? Loader : Plus} position="right" /> <ButtonIcon icon={saving ? Loader : Plus} position="right" />
</Button> </Button>
+13 -11
View File
@@ -22,15 +22,15 @@ export interface ProfileShadow {
blockingUri: string | undefined blockingUri: string | undefined
} }
type ProfileView = const shadows: WeakMap<
| AppBskyActorDefs.ProfileView AppBskyActorDefs.ProfileView,
| AppBskyActorDefs.ProfileViewBasic Partial<ProfileShadow>
| AppBskyActorDefs.ProfileViewDetailed > = new WeakMap()
const shadows: WeakMap<ProfileView, Partial<ProfileShadow>> = new WeakMap()
const emitter = new EventEmitter() const emitter = new EventEmitter()
export function useProfileShadow(profile: ProfileView): Shadow<ProfileView> { export function useProfileShadow<
TProfileView extends AppBskyActorDefs.ProfileView,
>(profile: TProfileView): Shadow<TProfileView> {
const [shadow, setShadow] = useState(() => shadows.get(profile)) const [shadow, setShadow] = useState(() => shadows.get(profile))
const [prevPost, setPrevPost] = useState(profile) const [prevPost, setPrevPost] = useState(profile)
if (profile !== prevPost) { if (profile !== prevPost) {
@@ -70,10 +70,10 @@ export function updateProfileShadow(
}) })
} }
function mergeShadow( function mergeShadow<TProfileView extends AppBskyActorDefs.ProfileView>(
profile: ProfileView, profile: TProfileView,
shadow: Partial<ProfileShadow>, shadow: Partial<ProfileShadow>,
): Shadow<ProfileView> { ): Shadow<TProfileView> {
return castAsShadow({ return castAsShadow({
...profile, ...profile,
viewer: { viewer: {
@@ -89,7 +89,9 @@ function mergeShadow(
}) })
} }
function* findProfilesInCache(did: string): Generator<ProfileView, void> { function* findProfilesInCache(
did: string,
): Generator<AppBskyActorDefs.ProfileView, void> {
yield* findAllProfilesInListMembersQueryData(queryClient, did) yield* findAllProfilesInListMembersQueryData(queryClient, did)
yield* findAllProfilesInMyBlockedAccountsQueryData(queryClient, did) yield* findAllProfilesInMyBlockedAccountsQueryData(queryClient, did)
yield* findAllProfilesInMyMutedAccountsQueryData(queryClient, did) yield* findAllProfilesInMyMutedAccountsQueryData(queryClient, did)
-13
View File
@@ -26,17 +26,6 @@ export interface EditProfileModal {
onUpdate?: () => void onUpdate?: () => void
} }
export interface ProfilePreviewModal {
name: 'profile-preview'
did: string
}
export interface ServerInputModal {
name: 'server-input'
initialService: string
onSelect: (url: string) => void
}
export interface ModerationDetailsModal { export interface ModerationDetailsModal {
name: 'moderation-details' name: 'moderation-details'
context: 'account' | 'content' context: 'account' | 'content'
@@ -202,7 +191,6 @@ export type Modal =
| ChangeHandleModal | ChangeHandleModal
| DeleteAccountModal | DeleteAccountModal
| EditProfileModal | EditProfileModal
| ProfilePreviewModal
| BirthDateSettingsModal | BirthDateSettingsModal
| VerifyEmailModal | VerifyEmailModal
| ChangeEmailModal | ChangeEmailModal
@@ -228,7 +216,6 @@ export type Modal =
| AltTextImageModal | AltTextImageModal
| CropImageModal | CropImageModal
| EditImageModal | EditImageModal
| ServerInputModal
| RepostModal | RepostModal
| SelfLabelModal | SelfLabelModal
| ThreadgateModal | ThreadgateModal
+1
View File
@@ -112,6 +112,7 @@ export function transform(legacy: Partial<LegacySchema>): Schema {
hiddenPosts: defaults.hiddenPosts, hiddenPosts: defaults.hiddenPosts,
externalEmbeds: defaults.externalEmbeds, externalEmbeds: defaults.externalEmbeds,
lastSelectedHomeFeed: defaults.lastSelectedHomeFeed, lastSelectedHomeFeed: defaults.lastSelectedHomeFeed,
pdsAddressHistory: defaults.pdsAddressHistory,
} }
} }
+2
View File
@@ -57,6 +57,7 @@ export const schema = z.object({
hiddenPosts: z.array(z.string()).optional(), // should move to server hiddenPosts: z.array(z.string()).optional(), // should move to server
useInAppBrowser: z.boolean().optional(), useInAppBrowser: z.boolean().optional(),
lastSelectedHomeFeed: z.string().optional(), lastSelectedHomeFeed: z.string().optional(),
pdsAddressHistory: z.array(z.string()).optional(),
}) })
export type Schema = z.infer<typeof schema> export type Schema = z.infer<typeof schema>
@@ -91,4 +92,5 @@ export const defaults: Schema = {
hiddenPosts: [], hiddenPosts: [],
useInAppBrowser: undefined, useInAppBrowser: undefined,
lastSelectedHomeFeed: undefined, lastSelectedHomeFeed: undefined,
pdsAddressHistory: [],
} }
+2 -2
View File
@@ -12,7 +12,7 @@ import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
import chunk from 'lodash.chunk' import chunk from 'lodash.chunk'
import {QueryClient} from '@tanstack/react-query' import {QueryClient} from '@tanstack/react-query'
import {getAgent} from '../../session' import {getAgent} from '../../session'
import {precacheProfile as precacheResolvedUri} from '../resolve-uri' import {precacheProfile} from '../profile'
import {NotificationType, FeedNotification, FeedPage} from './types' import {NotificationType, FeedNotification, FeedPage} from './types'
const GROUPABLE_REASONS = ['like', 'repost', 'follow'] const GROUPABLE_REASONS = ['like', 'repost', 'follow']
@@ -59,7 +59,7 @@ export async function fetchPage({
if (notif.subjectUri) { if (notif.subjectUri) {
notif.subject = subjects.get(notif.subjectUri) notif.subject = subjects.get(notif.subjectUri)
if (notif.subject) { if (notif.subject) {
precacheResolvedUri(queryClient, notif.subject.author) // precache the handle->did resolution precacheProfile(queryClient, notif.subject.author)
} }
} }
} }
+2 -2
View File
@@ -21,7 +21,7 @@ import {MergeFeedAPI} from 'lib/api/feed/merge'
import {HomeFeedAPI} from '#/lib/api/feed/home' import {HomeFeedAPI} from '#/lib/api/feed/home'
import {logger} from '#/logger' import {logger} from '#/logger'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import {precacheFeedPosts as precacheResolvedUris} from './resolve-uri' import {precacheFeedPostProfiles} from './profile'
import {getAgent} from '#/state/session' import {getAgent} from '#/state/session'
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const' import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
import {getModerationOpts} from '#/state/queries/preferences/moderation' import {getModerationOpts} from '#/state/queries/preferences/moderation'
@@ -138,7 +138,7 @@ export function usePostFeedQuery(
} }
const res = await api.fetch({cursor, limit: PAGE_SIZE}) const res = await api.fetch({cursor, limit: PAGE_SIZE})
precacheResolvedUris(queryClient, res.feed) // precache the handle->did resolution precacheFeedPostProfiles(queryClient, res.feed)
/* /*
* If this is a public view, we need to check if posts fail moderation. * If this is a public view, we need to check if posts fail moderation.
+2 -2
View File
@@ -10,7 +10,7 @@ import {getAgent} from '#/state/session'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {findPostInQueryData as findPostInFeedQueryData} from './post-feed' import {findPostInQueryData as findPostInFeedQueryData} from './post-feed'
import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed' import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed'
import {precacheThreadPosts as precacheResolvedUris} from './resolve-uri' import {precacheThreadPostProfiles} from './profile'
import {getEmbeddedPost} from './util' import {getEmbeddedPost} from './util'
export const RQKEY = (uri: string) => ['post-thread', uri] export const RQKEY = (uri: string) => ['post-thread', uri]
@@ -71,7 +71,7 @@ export function usePostThreadQuery(uri: string | undefined) {
const res = await getAgent().getPostThread({uri: uri!}) const res = await getAgent().getPostThread({uri: uri!})
if (res.success) { if (res.success) {
const nodes = responseToThreadNodes(res.data.thread) const nodes = responseToThreadNodes(res.data.thread)
precacheResolvedUris(queryClient, nodes) // precache the handle->did resolution precacheThreadPostProfiles(queryClient, nodes)
return nodes return nodes
} }
return {type: 'unknown', uri: uri!} return {type: 'unknown', uri: uri!}
+77 -3
View File
@@ -4,6 +4,9 @@ import {
AppBskyActorDefs, AppBskyActorDefs,
AppBskyActorProfile, AppBskyActorProfile,
AppBskyActorGetProfile, AppBskyActorGetProfile,
AppBskyFeedDefs,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
} from '@atproto/api' } from '@atproto/api'
import { import {
useQuery, useQuery,
@@ -23,9 +26,14 @@ import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts' import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import {track} from '#/lib/analytics/analytics' import {track} from '#/lib/analytics/analytics'
import {ThreadNode} from './post-thread'
export const RQKEY = (did: string) => ['profile', did] export const RQKEY = (did: string) => ['profile', did]
export const profilesQueryKey = (handles: string[]) => ['profiles', handles] export const profilesQueryKey = (handles: string[]) => ['profiles', handles]
export const profileBasicQueryKey = (didOrHandle: string) => [
'profileBasic',
didOrHandle,
]
export function useProfileQuery({ export function useProfileQuery({
did, did,
@@ -34,18 +42,26 @@ export function useProfileQuery({
did: string | undefined did: string | undefined
staleTime?: number staleTime?: number
}) { }) {
return useQuery({ const queryClient = useQueryClient()
return useQuery<AppBskyActorDefs.ProfileViewDetailed>({
// WARNING // WARNING
// this staleTime is load-bearing // this staleTime is load-bearing
// if you remove it, the UI infinite-loops // if you remove it, the UI infinite-loops
// -prf // -prf
staleTime, staleTime,
refetchOnWindowFocus: true, refetchOnWindowFocus: true,
queryKey: RQKEY(did || ''), queryKey: RQKEY(did ?? ''),
queryFn: async () => { queryFn: async () => {
const res = await getAgent().getProfile({actor: did || ''}) const res = await getAgent().getProfile({actor: did ?? ''})
return res.data return res.data
}, },
placeholderData: () => {
if (!did) return
return queryClient.getQueryData<AppBskyActorDefs.ProfileViewBasic>(
profileBasicQueryKey(did),
)
},
enabled: !!did, enabled: !!did,
}) })
} }
@@ -405,6 +421,64 @@ function useProfileUnblockMutation() {
}) })
} }
export function precacheProfile(
queryClient: QueryClient,
profile: AppBskyActorDefs.ProfileViewBasic,
) {
queryClient.setQueryData(profileBasicQueryKey(profile.handle), profile)
queryClient.setQueryData(profileBasicQueryKey(profile.did), profile)
}
export function precacheFeedPostProfiles(
queryClient: QueryClient,
posts: AppBskyFeedDefs.FeedViewPost[],
) {
for (const post of posts) {
// Save the author of the post every time
precacheProfile(queryClient, post.post.author)
precachePostEmbedProfile(queryClient, post.post.embed)
// Cache parent author and embeds
const parent = post.reply?.parent
if (AppBskyFeedDefs.isPostView(parent)) {
precacheProfile(queryClient, parent.author)
precachePostEmbedProfile(queryClient, parent.embed)
}
}
}
function precachePostEmbedProfile(
queryClient: QueryClient,
embed: AppBskyFeedDefs.PostView['embed'],
) {
if (AppBskyEmbedRecord.isView(embed)) {
if (AppBskyEmbedRecord.isViewRecord(embed.record)) {
precacheProfile(queryClient, embed.record.author)
}
} else if (AppBskyEmbedRecordWithMedia.isView(embed)) {
if (AppBskyEmbedRecord.isViewRecord(embed.record.record)) {
precacheProfile(queryClient, embed.record.record.author)
}
}
}
export function precacheThreadPostProfiles(
queryClient: QueryClient,
node: ThreadNode,
) {
if (node.type === 'post') {
precacheProfile(queryClient, node.post.author)
if (node.parent) {
precacheThreadPostProfiles(queryClient, node.parent)
}
if (node.replies?.length) {
for (const reply of node.replies) {
precacheThreadPostProfiles(queryClient, reply)
}
}
}
}
async function whenAppViewReady( async function whenAppViewReady(
actor: string, actor: string,
fn: (res: AppBskyActorGetProfile.Response) => boolean, fn: (res: AppBskyActorGetProfile.Response) => boolean,
+23 -49
View File
@@ -1,9 +1,9 @@
import {QueryClient, useQuery, UseQueryResult} from '@tanstack/react-query' import {useQuery, useQueryClient, UseQueryResult} from '@tanstack/react-query'
import {AtUri, AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' import {AtUri, AppBskyActorDefs} from '@atproto/api'
import {profileBasicQueryKey as RQKEY_PROFILE_BASIC} from './profile'
import {getAgent} from '#/state/session' import {getAgent} from '#/state/session'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import {ThreadNode} from './post-thread'
export const RQKEY = (didOrHandle: string) => ['resolved-did', didOrHandle] export const RQKEY = (didOrHandle: string) => ['resolved-did', didOrHandle]
@@ -22,55 +22,29 @@ export function useResolveUriQuery(uri: string | undefined): UriUseQueryResult {
} }
export function useResolveDidQuery(didOrHandle: string | undefined) { export function useResolveDidQuery(didOrHandle: string | undefined) {
const queryClient = useQueryClient()
return useQuery<string, Error>({ return useQuery<string, Error>({
staleTime: STALE.HOURS.ONE, staleTime: STALE.HOURS.ONE,
queryKey: RQKEY(didOrHandle || ''), queryKey: RQKEY(didOrHandle ?? ''),
async queryFn() { queryFn: async () => {
if (!didOrHandle) { if (!didOrHandle) return ''
return '' // Just return the did if it's already one
} if (didOrHandle.startsWith('did:')) return didOrHandle
if (!didOrHandle.startsWith('did:')) {
const res = await getAgent().resolveHandle({handle: didOrHandle}) const res = await getAgent().resolveHandle({handle: didOrHandle})
didOrHandle = res.data.did return res.data.did
} },
return didOrHandle initialData: () => {
// Return undefined if no did or handle
if (!didOrHandle) return
const profile =
queryClient.getQueryData<AppBskyActorDefs.ProfileViewBasic>(
RQKEY_PROFILE_BASIC(didOrHandle),
)
return profile?.did
}, },
enabled: !!didOrHandle, enabled: !!didOrHandle,
}) })
} }
export function precacheProfile(
queryClient: QueryClient,
profile:
| AppBskyActorDefs.ProfileView
| AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileViewDetailed,
) {
queryClient.setQueryData(RQKEY(profile.handle), profile.did)
}
export function precacheFeedPosts(
queryClient: QueryClient,
posts: AppBskyFeedDefs.FeedViewPost[],
) {
for (const post of posts) {
precacheProfile(queryClient, post.post.author)
}
}
export function precacheThreadPosts(
queryClient: QueryClient,
node: ThreadNode,
) {
if (node.type === 'post') {
precacheProfile(queryClient, node.post.author)
if (node.parent) {
precacheThreadPosts(queryClient, node.parent)
}
if (node.replies?.length) {
for (const reply of node.replies) {
precacheThreadPosts(queryClient, reply)
}
}
}
}
+61
View File
@@ -0,0 +1,61 @@
import React from 'react'
import * as persisted from '#/state/persisted'
import {isWeb} from '#/platform/detection'
type StateContext = string
type SetContext = (v: string) => void
const stateContext = React.createContext<StateContext>('home')
const setContext = React.createContext<SetContext>((_: string) => {})
function getInitialFeed() {
if (isWeb) {
if (window.location.pathname === '/') {
const params = new URLSearchParams(window.location.search)
const feedFromUrl = params.get('feed')
if (feedFromUrl) {
// If explicitly booted from a link like /?feed=..., prefer that.
return feedFromUrl
}
}
const feedFromSession = sessionStorage.getItem('lastSelectedHomeFeed')
if (feedFromSession) {
// Fall back to a previously chosen feed for this browser tab.
return feedFromSession
}
}
const feedFromPersisted = persisted.get('lastSelectedHomeFeed')
if (feedFromPersisted) {
// Fall back to the last chosen one across all tabs.
return feedFromPersisted
}
return 'home'
}
export function Provider({children}: React.PropsWithChildren<{}>) {
const [state, setState] = React.useState(getInitialFeed)
const saveState = React.useCallback((feed: string) => {
setState(feed)
if (isWeb) {
try {
sessionStorage.setItem('lastSelectedHomeFeed', feed)
} catch {}
}
persisted.write('lastSelectedHomeFeed', feed)
}, [])
return (
<stateContext.Provider value={state}>
<setContext.Provider value={saveState}>{children}</setContext.Provider>
</stateContext.Provider>
)
}
export function useSelectedFeed() {
return React.useContext(stateContext)
}
export function useSetSelectedFeed() {
return React.useContext(setContext)
}
+3 -3
View File
@@ -83,19 +83,19 @@ export function HomeLoggedOutCTA() {
<View style={[styles.footer, pal.view, pal.border]}> <View style={[styles.footer, pal.view, pal.border]}>
<TextLink <TextLink
type="2xl" type="2xl"
href="https://blueskyweb.xyz" href="https://bsky.social"
text={_(msg`Business`)} text={_(msg`Business`)}
style={[styles.footerLink, pal.link]} style={[styles.footerLink, pal.link]}
/> />
<TextLink <TextLink
type="2xl" type="2xl"
href="https://blueskyweb.xyz/blog" href="https://bsky.social/about/blog"
text={_(msg`Blog`)} text={_(msg`Blog`)}
style={[styles.footerLink, pal.link]} style={[styles.footerLink, pal.link]}
/> />
<TextLink <TextLink
type="2xl" type="2xl"
href="https://blueskyweb.xyz/join" href="https://bsky.social/about/join"
text={_(msg`Jobs`)} text={_(msg`Jobs`)}
style={[styles.footerLink, pal.link]} style={[styles.footerLink, pal.link]}
/> />
+3 -3
View File
@@ -102,17 +102,17 @@ function Footer({styles}: {styles: ReturnType<typeof useStyles>}) {
return ( return (
<View style={[styles.footer, pal.view, pal.border]}> <View style={[styles.footer, pal.view, pal.border]}>
<TextLink <TextLink
href="https://blueskyweb.xyz" href="https://bsky.social"
text="Business" text="Business"
style={[styles.footerLink, pal.link]} style={[styles.footerLink, pal.link]}
/> />
<TextLink <TextLink
href="https://blueskyweb.xyz/blog" href="https://bsky.social/about/blog"
text="Blog" text="Blog"
style={[styles.footerLink, pal.link]} style={[styles.footerLink, pal.link]}
/> />
<TextLink <TextLink
href="https://blueskyweb.xyz/join" href="https://bsky.social/about/join"
text="Jobs" text="Jobs"
style={[styles.footerLink, pal.link]} style={[styles.footerLink, pal.link]}
/> />
+77 -25
View File
@@ -3,6 +3,7 @@ import {
ActivityIndicator, ActivityIndicator,
Keyboard, Keyboard,
StyleSheet, StyleSheet,
TouchableOpacity,
TouchableWithoutFeedback, TouchableWithoutFeedback,
View, View,
} from 'react-native' } from 'react-native'
@@ -13,7 +14,6 @@ import {StepHeader} from './StepHeader'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {TextInput} from '../util/TextInput' import {TextInput} from '../util/TextInput'
import {Button} from '../../util/forms/Button'
import {Policies} from './Policies' import {Policies} from './Policies'
import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
@@ -21,7 +21,14 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {logger} from '#/logger' import {logger} from '#/logger'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {useDialogControl} from '#/components/Dialog'
import {ServerInputDialog} from '../server-input'
import {toNiceDomain} from '#/lib/strings/url-helpers'
function sanitizeDate(date: Date): Date { function sanitizeDate(date: Date): Date {
if (!date || date.toString() === 'Invalid Date') { if (!date || date.toString() === 'Invalid Date') {
@@ -43,16 +50,12 @@ export function Step1({
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {openModal} = useModalControls() const {openModal} = useModalControls()
const serverInputControl = useDialogControl()
const onPressSelectService = React.useCallback(() => { const onPressSelectService = React.useCallback(() => {
openModal({ serverInputControl.open()
name: 'server-input',
initialService: uiState.serviceUrl,
onSelect: (url: string) =>
uiDispatch({type: 'set-service-url', value: url}),
})
Keyboard.dismiss() Keyboard.dismiss()
}, [uiDispatch, uiState.serviceUrl, openModal]) }, [serverInputControl])
const onPressWaitlist = React.useCallback(() => { const onPressWaitlist = React.useCallback(() => {
openModal({name: 'waitlist'}) openModal({name: 'waitlist'})
@@ -64,23 +67,72 @@ export function Step1({
return ( return (
<View> <View>
<StepHeader uiState={uiState} title={_(msg`Your account`)}> <ServerInputDialog
<View> control={serverInputControl}
<Button onSelect={url => uiDispatch({type: 'set-service-url', value: url})}
testID="selectServiceButton" />
type="default" <StepHeader uiState={uiState} title={_(msg`Your account`)} />
style={{
aspectRatio: 1, <View style={s.pb20}>
justifyContent: 'center', <Text type="md-medium" style={[pal.text, s.mb2]}>
alignItems: 'center', <Trans>Hosting provider</Trans>
}} </Text>
accessibilityLabel={_(msg`Select service`)} <View style={[pal.border, {borderWidth: 1, borderRadius: 6}]}>
accessibilityHint={_(msg`Sets server for the Bluesky client`)} <View
onPress={onPressSelectService}> style={[
<FontAwesomeIcon icon="server" size={21} color={pal.colors.text} /> pal.borderDark,
</Button> {flexDirection: 'row', alignItems: 'center'},
]}>
<FontAwesomeIcon
icon="globe"
style={[pal.textLight, {marginLeft: 14}]}
/>
<TouchableOpacity
testID="loginSelectServiceButton"
style={{
flexDirection: 'row',
flex: 1,
alignItems: 'center',
}}
onPress={onPressSelectService}
accessibilityRole="button"
accessibilityLabel={_(msg`Select service`)}
accessibilityHint={_(msg`Sets server for the Bluesky client`)}>
<Text
type="xl"
style={[
pal.text,
{
flex: 1,
paddingVertical: 10,
paddingRight: 12,
paddingLeft: 10,
},
]}>
{toNiceDomain(uiState.serviceUrl)}
</Text>
<View
style={[
pal.btn,
{
flexDirection: 'row',
alignItems: 'center',
borderRadius: 6,
paddingVertical: 6,
paddingHorizontal: 8,
marginHorizontal: 6,
},
]}>
<FontAwesomeIcon
icon="pen"
size={12}
style={pal.textLight as FontAwesomeIconStyle}
/>
</View>
</TouchableOpacity>
</View>
</View> </View>
</StepHeader> </View>
{!uiState.serviceDescription ? ( {!uiState.serviceDescription ? (
<ActivityIndicator /> <ActivityIndicator />
+13 -9
View File
@@ -1,6 +1,7 @@
import React, {useState, useEffect} from 'react' import React, {useState, useEffect} from 'react'
import { import {
ActivityIndicator, ActivityIndicator,
Keyboard,
TextInput, TextInput,
TouchableOpacity, TouchableOpacity,
View, View,
@@ -24,7 +25,9 @@ import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {styles} from './styles' import {styles} from './styles'
import {useModalControls} from '#/state/modals' import {useDialogControl} from '#/components/Dialog'
import {ServerInputDialog} from '../server-input'
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
@@ -51,19 +54,16 @@ export const ForgotPasswordForm = ({
const [email, setEmail] = useState<string>('') const [email, setEmail] = useState<string>('')
const {screen} = useAnalytics() const {screen} = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {openModal} = useModalControls() const serverInputControl = useDialogControl()
useEffect(() => { useEffect(() => {
screen('Signin:ForgotPassword') screen('Signin:ForgotPassword')
}, [screen]) }, [screen])
const onPressSelectService = () => { const onPressSelectService = React.useCallback(() => {
openModal({ serverInputControl.open()
name: 'server-input', Keyboard.dismiss()
initialService: serviceUrl, }, [serverInputControl])
onSelect: setServiceUrl,
})
}
const onPressNext = async () => { const onPressNext = async () => {
if (!EmailValidator.validate(email)) { if (!EmailValidator.validate(email)) {
@@ -96,6 +96,10 @@ export const ForgotPasswordForm = ({
return ( return (
<> <>
<View> <View>
<ServerInputDialog
control={serverInputControl}
onSelect={setServiceUrl}
/>
<Text type="title-lg" style={[pal.text, styles.screenTitle]}> <Text type="title-lg" style={[pal.text, styles.screenTitle]}>
<Trans>Reset password</Trans> <Trans>Reset password</Trans>
</Text> </Text>
+10 -7
View File
@@ -25,7 +25,9 @@ import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {styles} from './styles' import {styles} from './styles'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {useDialogControl} from '#/components/Dialog'
import {ServerInputDialog} from '../server-input'
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
@@ -58,15 +60,11 @@ export const LoginForm = ({
const [password, setPassword] = useState<string>('') const [password, setPassword] = useState<string>('')
const passwordInputRef = useRef<TextInput>(null) const passwordInputRef = useRef<TextInput>(null)
const {_} = useLingui() const {_} = useLingui()
const {openModal} = useModalControls()
const {login} = useSessionApi() const {login} = useSessionApi()
const serverInputControl = useDialogControl()
const onPressSelectService = () => { const onPressSelectService = () => {
openModal({ serverInputControl.open()
name: 'server-input',
initialService: serviceUrl,
onSelect: setServiceUrl,
})
Keyboard.dismiss() Keyboard.dismiss()
track('Signin:PressedSelectService') track('Signin:PressedSelectService')
} }
@@ -130,6 +128,11 @@ export const LoginForm = ({
const isReady = !!serviceDescription && !!identifier && !!password const isReady = !!serviceDescription && !!identifier && !!password
return ( return (
<View testID="loginForm"> <View testID="loginForm">
<ServerInputDialog
control={serverInputControl}
onSelect={setServiceUrl}
/>
<Text type="sm-bold" style={[pal.text, styles.groupLabel]}> <Text type="sm-bold" style={[pal.text, styles.groupLabel]}>
<Trans>Sign into</Trans> <Trans>Sign into</Trans>
</Text> </Text>
+173
View File
@@ -0,0 +1,173 @@
import React from 'react'
import {View} from 'react-native'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {PROD_SERVICE} from 'lib/constants'
import * as persisted from '#/state/persisted'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {Text, P} from '#/components/Typography'
import {Button, ButtonText} from '#/components/Button'
import * as ToggleButton from '#/components/forms/ToggleButton'
import * as TextField from '#/components/forms/TextField'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
export function ServerInputDialog({
control,
onSelect,
}: {
control: Dialog.DialogOuterProps['control']
onSelect: (url: string) => void
}) {
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const [pdsAddressHistory, setPdsAddressHistory] = React.useState<string[]>(
persisted.get('pdsAddressHistory') || [],
)
const [fixedOption, setFixedOption] = React.useState([PROD_SERVICE])
const [customAddress, setCustomAddress] = React.useState('')
const onClose = React.useCallback(() => {
let url
if (fixedOption[0] === 'custom') {
url = customAddress.trim().toLowerCase()
if (!url) {
return
}
} else {
url = fixedOption[0]
}
if (!url.startsWith('http://') && !url.startsWith('https://')) {
if (url === 'localhost' || url.startsWith('localhost:')) {
url = `http://${url}`
} else {
url = `https://${url}`
}
}
if (fixedOption[0] === 'custom') {
if (!pdsAddressHistory.includes(url)) {
const newHistory = [url, ...pdsAddressHistory.slice(0, 4)]
setPdsAddressHistory(newHistory)
persisted.write('pdsAddressHistory', newHistory)
}
}
onSelect(url)
}, [
fixedOption,
customAddress,
onSelect,
pdsAddressHistory,
setPdsAddressHistory,
])
return (
<Dialog.Outer
control={control}
nativeOptions={{sheet: {snapPoints: ['100%']}}}
onClose={onClose}>
<Dialog.Handle />
<Dialog.ScrollableInner
accessibilityDescribedBy="dialog-description"
accessibilityLabelledBy="dialog-title">
<View style={[a.relative, a.gap_md, a.w_full]}>
<Text nativeID="dialog-title" style={[a.text_2xl, a.font_bold]}>
<Trans>Choose Service</Trans>
</Text>
<P nativeID="dialog-description" style={[a.text_sm]}>
<Trans>Select the service that hosts your data.</Trans>
</P>
<ToggleButton.Group
label="Preferences"
values={fixedOption}
onChange={setFixedOption}>
<ToggleButton.Button name={PROD_SERVICE} label={_(msg`Bluesky`)}>
{_(msg`Bluesky`)}
</ToggleButton.Button>
<ToggleButton.Button
testID="customSelectBtn"
name="custom"
label={_(msg`Custom`)}>
{_(msg`Custom`)}
</ToggleButton.Button>
</ToggleButton.Group>
{fixedOption[0] === 'custom' && (
<View
style={[
a.border,
t.atoms.border_contrast_low,
a.rounded_sm,
a.px_md,
a.py_md,
]}>
<TextField.Label nativeID="address-input-label">
<Trans>Server address</Trans>
</TextField.Label>
<TextField.Root>
<TextField.Icon icon={Globe} />
<Dialog.Input
testID="customServerTextInput"
value={customAddress}
onChangeText={setCustomAddress}
label={_(msg`my-server.com`)}
accessibilityLabelledBy="address-input-label"
autoCapitalize="none"
keyboardType="url"
/>
</TextField.Root>
{pdsAddressHistory.length > 0 && (
<View style={[a.flex_row, a.flex_wrap, a.mt_xs]}>
{pdsAddressHistory.map(uri => (
<Button
key={uri}
variant="ghost"
color="primary"
label={uri}
style={[a.px_sm, a.py_xs, a.rounded_sm, a.gap_sm]}
onPress={() => setCustomAddress(uri)}>
<ButtonText>{uri}</ButtonText>
</Button>
))}
</View>
)}
</View>
)}
<View style={[a.py_xs]}>
<P
style={[
t.atoms.text_contrast_medium,
a.text_sm,
a.leading_snug,
a.flex_1,
]}>
<Trans>
Bluesky is an open network where you can choose your hosting
provider. Custom hosting is now available in beta for
developers.
</Trans>
</P>
</View>
<View style={gtMobile && [a.flex_row, a.justify_end]}>
<Button
testID="doneBtn"
variant="outline"
color="primary"
size="small"
onPress={() => control.close()}
label={_(msg`Done`)}>
{_(msg`Done`)}
</Button>
</View>
</View>
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
@@ -28,6 +28,7 @@ import {useTheme} from 'lib/ThemeContext'
import {isUriImage} from 'lib/media/util' import {isUriImage} from 'lib/media/util'
import {downloadAndResize} from 'lib/media/manip' import {downloadAndResize} from 'lib/media/manip'
import {POST_IMG_MAX} from 'lib/constants' import {POST_IMG_MAX} from 'lib/constants'
import {isIOS} from 'platform/detection'
export interface TextInputRef { export interface TextInputRef {
focus: () => void focus: () => void
@@ -252,6 +253,7 @@ const styles = StyleSheet.create({
fontSize: 18, fontSize: 18,
letterSpacing: 0.2, letterSpacing: 0.2,
fontWeight: '400', fontWeight: '400',
lineHeight: 23.4, // 1.3*16 // This is broken on ios right now, so don't set it there.
lineHeight: isIOS ? undefined : 23.4, // 1.3*16
}, },
}) })
@@ -228,7 +228,10 @@ export const TextInput = React.forwardRef(function TextInputImpl(
return ( return (
<> <>
<View style={styles.container}> <View style={styles.container}>
<EditorContent editor={editor} /> <EditorContent
editor={editor}
style={{color: pal.text.color as string}}
/>
</View> </View>
{isDropping && ( {isDropping && (
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import {LabelPreference} from '@atproto/api' import {LabelPreference} from '@atproto/api'
import {StyleSheet, Pressable, View} from 'react-native' import {StyleSheet, Pressable, View, Linking} from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {ScrollView} from './util' import {ScrollView} from './util'
import {s, colors, gradients} from 'lib/styles' import {s, colors, gradients} from 'lib/styles'
@@ -129,6 +129,10 @@ function AdultContentEnabledPref() {
} }
}, [variables, preferences, mutate, _]) }, [variables, preferences, mutate, _])
const onAdultContentLinkPress = React.useCallback(() => {
Linking.openURL('https://bsky.app/')
}, [])
return ( return (
<View style={s.mb10}> <View style={s.mb10}>
{isIOS ? ( {isIOS ? (
@@ -138,8 +142,9 @@ function AdultContentEnabledPref() {
Adult content can only be enabled via the Web at{' '} Adult content can only be enabled via the Web at{' '}
<TextLink <TextLink
style={pal.link} style={pal.link}
href="https://bsky.app" href=""
text="bsky.app" text="bsky.app"
onPress={onAdultContentLinkPress}
/> />
. .
</Trans> </Trans>
+2 -39
View File
@@ -1,19 +1,13 @@
import React, {useRef, useEffect} from 'react' import React, {useRef, useEffect} from 'react'
import {StyleSheet} from 'react-native' import {StyleSheet} from 'react-native'
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context' import {SafeAreaView} from 'react-native-safe-area-context'
import BottomSheet from '@gorhom/bottom-sheet' import BottomSheet from '@gorhom/bottom-sheet'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {timeout} from 'lib/async/timeout'
import {navigate} from '../../../Navigation'
import once from 'lodash.once'
import {useModals, useModalControls} from '#/state/modals' import {useModals, useModalControls} from '#/state/modals'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import * as ConfirmModal from './Confirm' import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile' import * as EditProfileModal from './EditProfile'
import * as ProfilePreviewModal from './ProfilePreview'
import * as ServerInputModal from './ServerInput'
import * as RepostModal from './Repost' import * as RepostModal from './Repost'
import * as SelfLabelModal from './SelfLabel' import * as SelfLabelModal from './SelfLabel'
import * as ThreadgateModal from './Threadgate' import * as ThreadgateModal from './Threadgate'
@@ -50,34 +44,14 @@ export function ModalsContainer() {
const {closeModal} = useModalControls() const {closeModal} = useModalControls()
const bottomSheetRef = useRef<BottomSheet>(null) const bottomSheetRef = useRef<BottomSheet>(null)
const pal = usePalette('default') const pal = usePalette('default')
const safeAreaInsets = useSafeAreaInsets()
const activeModal = activeModals[activeModals.length - 1] const activeModal = activeModals[activeModals.length - 1]
const navigateOnce = once(navigate)
// It seems like the bottom sheet bugs out when this callback changes.
const onBottomSheetAnimate = useNonReactiveCallback(
(_fromIndex: number, toIndex: number) => {
if (activeModal?.name === 'profile-preview' && toIndex === 1) {
// begin loading the profile screen behind the scenes
navigateOnce('Profile', {name: activeModal.did})
}
},
)
const onBottomSheetChange = async (snapPoint: number) => { const onBottomSheetChange = async (snapPoint: number) => {
if (snapPoint === -1) { if (snapPoint === -1) {
closeModal() closeModal()
} else if (activeModal?.name === 'profile-preview' && snapPoint === 1) {
await navigateOnce('Profile', {name: activeModal.did})
// There is no particular callback for when the view has actually been presented.
// This delay gives us a decent chance the navigation has flushed *and* images have loaded.
// It's acceptable because the data is already being fetched + it usually takes longer anyway.
// TODO: Figure out why avatar/cover don't always show instantly from cache.
await timeout(200)
closeModal()
} }
} }
const onClose = () => { const onClose = () => {
bottomSheetRef.current?.close() bottomSheetRef.current?.close()
closeModal() closeModal()
@@ -91,7 +65,6 @@ export function ModalsContainer() {
} }
}, [isModalActive, bottomSheetRef, activeModal?.name]) }, [isModalActive, bottomSheetRef, activeModal?.name])
let needsSafeTopInset = false
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
let element let element
if (activeModal?.name === 'confirm') { if (activeModal?.name === 'confirm') {
@@ -100,13 +73,6 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'edit-profile') { } else if (activeModal?.name === 'edit-profile') {
snapPoints = EditProfileModal.snapPoints snapPoints = EditProfileModal.snapPoints
element = <EditProfileModal.Component {...activeModal} /> element = <EditProfileModal.Component {...activeModal} />
} else if (activeModal?.name === 'profile-preview') {
snapPoints = ProfilePreviewModal.snapPoints
element = <ProfilePreviewModal.Component {...activeModal} />
needsSafeTopInset = true // Need to align with the target profile screen.
} else if (activeModal?.name === 'server-input') {
snapPoints = ServerInputModal.snapPoints
element = <ServerInputModal.Component {...activeModal} />
} else if (activeModal?.name === 'report') { } else if (activeModal?.name === 'report') {
snapPoints = ReportModal.snapPoints snapPoints = ReportModal.snapPoints
element = <ReportModal.Component {...activeModal} /> element = <ReportModal.Component {...activeModal} />
@@ -200,12 +166,10 @@ export function ModalsContainer() {
) )
} }
const topInset = needsSafeTopInset ? safeAreaInsets.top - HANDLE_HEIGHT : 0
return ( return (
<BottomSheet <BottomSheet
ref={bottomSheetRef} ref={bottomSheetRef}
snapPoints={snapPoints} snapPoints={snapPoints}
topInset={topInset}
handleHeight={HANDLE_HEIGHT} handleHeight={HANDLE_HEIGHT}
index={isModalActive ? 0 : -1} index={isModalActive ? 0 : -1}
enablePanDownToClose enablePanDownToClose
@@ -216,7 +180,6 @@ export function ModalsContainer() {
} }
handleIndicatorStyle={{backgroundColor: pal.text.color}} handleIndicatorStyle={{backgroundColor: pal.text.color}}
handleStyle={[styles.handle, pal.view]} handleStyle={[styles.handle, pal.view]}
onAnimate={onBottomSheetAnimate}
onChange={onBottomSheetChange}> onChange={onBottomSheetChange}>
{element} {element}
</BottomSheet> </BottomSheet>
-6
View File
@@ -9,8 +9,6 @@ import {useModals, useModalControls} from '#/state/modals'
import type {Modal as ModalIface} from '#/state/modals' import type {Modal as ModalIface} from '#/state/modals'
import * as ConfirmModal from './Confirm' import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile' import * as EditProfileModal from './EditProfile'
import * as ProfilePreviewModal from './ProfilePreview'
import * as ServerInputModal from './ServerInput'
import * as ReportModal from './report/Modal' import * as ReportModal from './report/Modal'
import * as AppealLabelModal from './AppealLabel' import * as AppealLabelModal from './AppealLabel'
import * as CreateOrEditListModal from './CreateOrEditList' import * as CreateOrEditListModal from './CreateOrEditList'
@@ -85,10 +83,6 @@ function Modal({modal}: {modal: ModalIface}) {
element = <ConfirmModal.Component {...modal} /> element = <ConfirmModal.Component {...modal} />
} else if (modal.name === 'edit-profile') { } else if (modal.name === 'edit-profile') {
element = <EditProfileModal.Component {...modal} /> element = <EditProfileModal.Component {...modal} />
} else if (modal.name === 'profile-preview') {
element = <ProfilePreviewModal.Component {...modal} />
} else if (modal.name === 'server-input') {
element = <ServerInputModal.Component {...modal} />
} else if (modal.name === 'report') { } else if (modal.name === 'report') {
element = <ReportModal.Component {...modal} /> element = <ReportModal.Component {...modal} />
} else if (modal.name === 'appeal-label') { } else if (modal.name === 'appeal-label') {
-134
View File
@@ -1,134 +0,0 @@
import React, {useState, useEffect} from 'react'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs, ModerationOpts, moderateProfile} from '@atproto/api'
import {ThemedText} from '../util/text/ThemedText'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
import {ProfileHeader} from '../profile/ProfileHeader'
import {InfoCircleIcon} from 'lib/icons'
import {useNavigationState} from '@react-navigation/native'
import {s} from 'lib/styles'
import {useModerationOpts} from '#/state/queries/preferences'
import {useProfileQuery} from '#/state/queries/profile'
import {ErrorScreen} from '../util/error/ErrorScreen'
import {CenteredView} from '../util/Views'
import {cleanError} from '#/lib/strings/errors'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export const snapPoints = [520, '100%']
export function Component({did}: {did: string}) {
const pal = usePalette('default')
const {_} = useLingui()
const moderationOpts = useModerationOpts()
const {
data: profile,
error: profileError,
refetch: refetchProfile,
isLoading: isLoadingProfile,
} = useProfileQuery({
did: did,
})
if (isLoadingProfile || !moderationOpts) {
return (
<CenteredView style={[pal.view, s.flex1]}>
<ProfileHeader
profile={null}
moderation={null}
isProfilePreview={true}
/>
</CenteredView>
)
}
if (profileError) {
return (
<ErrorScreen
title={_(msg`Oops!`)}
message={cleanError(profileError)}
onPressTryAgain={refetchProfile}
/>
)
}
if (profile && moderationOpts) {
return <ComponentLoaded profile={profile} moderationOpts={moderationOpts} />
}
// should never happen
return (
<ErrorScreen
title={_(msg`Oops!`)}
message={_(msg`Something went wrong and we're not sure what.`)}
onPressTryAgain={refetchProfile}
/>
)
}
function ComponentLoaded({
profile: profileUnshadowed,
moderationOpts,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
}) {
const pal = usePalette('default')
const profile = useProfileShadow(profileUnshadowed)
const {screen} = useAnalytics()
const moderation = React.useMemo(
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
// track the navigator state to detect if a page-load occurred
const navState = useNavigationState(state => state)
const [initNavState] = useState(navState)
const isLoading = initNavState !== navState
useEffect(() => {
screen('Profile:Preview')
}, [screen])
return (
<View testID="profilePreview" style={[pal.view, s.flex1]}>
<View style={[styles.headerWrapper]}>
<ProfileHeader
profile={profile}
moderation={moderation}
hideBackButton
isProfilePreview
/>
</View>
<View style={[styles.hintWrapper, pal.view]}>
<View style={styles.hint}>
{isLoading ? (
<ActivityIndicator />
) : (
<>
<InfoCircleIcon size={21} style={pal.textLight} />
<ThemedText type="xl" fg="light">
<Trans>Swipe up to see more</Trans>
</ThemedText>
</>
)}
</View>
</View>
</View>
)
}
const styles = StyleSheet.create({
headerWrapper: {
height: 440,
},
hintWrapper: {
height: 80,
},
hint: {
flexDirection: 'row',
justifyContent: 'center',
gap: 8,
paddingHorizontal: 14,
borderRadius: 6,
},
})
-189
View File
@@ -1,189 +0,0 @@
import React, {useState} from 'react'
import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {ScrollView, TextInput} from './util'
import {Text} from '../util/text/Text'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'lib/constants'
import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%']
export function Component({onSelect}: {onSelect: (url: string) => void}) {
const theme = useTheme()
const pal = usePalette('default')
const [customUrl, setCustomUrl] = useState<string>('')
const {_} = useLingui()
const {closeModal} = useModalControls()
const doSelect = (url: string) => {
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `https://${url}`
}
closeModal()
onSelect(url)
}
return (
<View style={[pal.view, s.flex1]} testID="serverInputModal">
<Text type="2xl-bold" style={[pal.text, s.textCenter]}>
<Trans>Choose Service</Trans>
</Text>
<ScrollView style={styles.inner}>
<View style={styles.group}>
{LOGIN_INCLUDE_DEV_SERVERS ? (
<>
<TouchableOpacity
testID="localDevServerButton"
style={styles.btn}
onPress={() => doSelect(LOCAL_DEV_SERVICE)}
accessibilityRole="button">
<Text style={styles.btnText}>
<Trans>Local dev server</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.btn}
onPress={() => doSelect(STAGING_SERVICE)}
accessibilityRole="button">
<Text style={styles.btnText}>
<Trans>Staging</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity>
</>
) : undefined}
<TouchableOpacity
style={styles.btn}
onPress={() => doSelect(PROD_SERVICE)}
accessibilityRole="button"
accessibilityLabel={_(msg`Select Bluesky Social`)}
accessibilityHint="Sets Bluesky Social as your service provider">
<Text style={styles.btnText}>
<Trans>Bluesky.Social</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity>
</View>
<View style={styles.group}>
<Text style={[pal.text, styles.label]}>
<Trans>Other service</Trans>
</Text>
<View style={s.flexRow}>
<TextInput
testID="customServerTextInput"
style={[pal.borderDark, pal.text, styles.textInput]}
placeholder="e.g. https://bsky.app"
placeholderTextColor={colors.gray4}
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
keyboardAppearance={theme.colorScheme}
value={customUrl}
onChangeText={setCustomUrl}
accessibilityLabel={_(msg`Custom domain`)}
// TODO: Simplify this wording further to be understandable by everyone
accessibilityHint={_(
msg`Use your domain as your Bluesky client service provider`,
)}
/>
<TouchableOpacity
testID="customServerSelectBtn"
style={[pal.borderDark, pal.text, styles.textInputBtn]}
onPress={() => doSelect(customUrl)}
accessibilityRole="button"
accessibilityLabel={`Confirm service. ${
customUrl === ''
? _(msg`Button disabled. Input custom domain to proceed.`)
: ''
}`}
accessibilityHint=""
// TODO - accessibility: Need to inform state change on failure
disabled={customUrl === ''}>
<FontAwesomeIcon
icon="check"
style={[pal.text as FontAwesomeIconStyle, styles.checkIcon]}
size={18}
/>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
inner: {
padding: 14,
},
group: {
marginBottom: 20,
},
label: {
fontWeight: 'bold',
paddingHorizontal: 4,
paddingBottom: 4,
},
textInput: {
flex: 1,
borderWidth: 1,
borderTopLeftRadius: 6,
borderBottomLeftRadius: 6,
paddingHorizontal: 14,
paddingVertical: 12,
fontSize: 16,
},
textInputBtn: {
borderWidth: 1,
borderLeftWidth: 0,
borderTopRightRadius: 6,
borderBottomRightRadius: 6,
paddingHorizontal: 14,
paddingVertical: 10,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.blue3,
borderRadius: 6,
paddingHorizontal: 14,
paddingVertical: 10,
marginBottom: 6,
},
btnText: {
flex: 1,
fontSize: 18,
fontWeight: '500',
color: colors.white,
},
checkIcon: {
position: 'relative',
...Platform.select({
android: {
top: 8,
},
ios: {
top: 2,
},
}),
},
})
+1 -1
View File
@@ -18,7 +18,7 @@ import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {getAgent} from '#/state/session' import {getAgent} from '#/state/session'
const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright' const DMCA_LINK = 'https://bsky.social/about/support/copyright'
export const snapPoints = [575] export const snapPoints = [575]
+1 -1
View File
@@ -31,7 +31,7 @@ export const Pager = React.forwardRef(function PagerImpl(
const anchorRef = React.useRef(null) const anchorRef = React.useRef(null)
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
setPage: (index: number) => setSelectedPage(index), setPage: (index: number) => onTabBarSelect(index),
})) }))
const onTabBarSelect = React.useCallback( const onTabBarSelect = React.useCallback(
+43 -22
View File
@@ -61,26 +61,19 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
const headerHeight = headerOnlyHeight + tabBarHeight const headerHeight = headerOnlyHeight + tabBarHeight
// capture the header bar sizing // capture the header bar sizing
const onTabBarLayout = React.useCallback( const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
(evt: LayoutChangeEvent) => { const height = evt.nativeEvent.layout.height
const height = evt.nativeEvent.layout.height if (height > 0) {
if (height > 0) { // The rounding is necessary to prevent jumps on iOS
// The rounding is necessary to prevent jumps on iOS setTabBarHeight(Math.round(height))
setTabBarHeight(Math.round(height)) }
} })
}, const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => {
[setTabBarHeight], if (height > 0) {
) // The rounding is necessary to prevent jumps on iOS
const onHeaderOnlyLayout = React.useCallback( setHeaderOnlyHeight(Math.round(height))
(evt: LayoutChangeEvent) => { }
const height = evt.nativeEvent.layout.height })
if (height > 0) {
// The rounding is necessary to prevent jumps on iOS
setHeaderOnlyHeight(Math.round(height))
}
},
[setHeaderOnlyHeight],
)
const renderTabBar = React.useCallback( const renderTabBar = React.useCallback(
(props: RenderTabBarFnProps) => { (props: RenderTabBarFnProps) => {
@@ -228,7 +221,7 @@ let PagerTabBar = ({
testID?: string testID?: string
scrollY: SharedValue<number> scrollY: SharedValue<number>
renderHeader?: () => JSX.Element renderHeader?: () => JSX.Element
onHeaderOnlyLayout: (e: LayoutChangeEvent) => void onHeaderOnlyLayout: (height: number) => void
onTabBarLayout: (e: LayoutChangeEvent) => void onTabBarLayout: (e: LayoutChangeEvent) => void
onCurrentPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void
onSelect?: (index: number) => void onSelect?: (index: number) => void
@@ -240,12 +233,40 @@ let PagerTabBar = ({
}, },
], ],
})) }))
const pendingHeaderHeight = React.useRef<null | number>(null)
return ( return (
<Animated.View <Animated.View
pointerEvents="box-none" pointerEvents="box-none"
style={[styles.tabBarMobile, headerTransform]}> style={[styles.tabBarMobile, headerTransform]}>
<View onLayout={onHeaderOnlyLayout} pointerEvents="box-none"> <View
pointerEvents="box-none"
collapsable={false}
onLayout={e => {
if (isHeaderReady) {
onHeaderOnlyLayout(e.nativeEvent.layout.height)
pendingHeaderHeight.current = null
} else {
// Stash it away for when `isHeaderReady` turns `true` later.
pendingHeaderHeight.current = e.nativeEvent.layout.height
}
}}>
{renderHeader?.()} {renderHeader?.()}
{
// When `isHeaderReady` turns `true`, we want to send the parent layout.
// However, if that didn't lead to a layout change, parent `onLayout` wouldn't get called again.
// We're conditionally rendering an empty view so that we can send the last measurement.
isHeaderReady && (
<View
onLayout={() => {
// We're assuming the parent `onLayout` already ran (parent -> child ordering).
if (pendingHeaderHeight.current !== null) {
onHeaderOnlyLayout(pendingHeaderHeight.current)
pendingHeaderHeight.current = null
}
}}
/>
)
}
</View> </View>
<View <View
onLayout={onTabBarLayout} onLayout={onTabBarLayout}
+31 -3
View File
@@ -31,6 +31,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
children, children,
testID, testID,
items, items,
isHeaderReady,
renderHeader, renderHeader,
initialPage, initialPage,
onPageSelected, onPageSelected,
@@ -46,6 +47,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
<PagerTabBar <PagerTabBar
items={items} items={items}
renderHeader={renderHeader} renderHeader={renderHeader}
isHeaderReady={isHeaderReady}
currentPage={currentPage} currentPage={currentPage}
onCurrentPageSelected={onCurrentPageSelected} onCurrentPageSelected={onCurrentPageSelected}
onSelect={props.onSelect} onSelect={props.onSelect}
@@ -54,7 +56,14 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
/> />
) )
}, },
[items, renderHeader, currentPage, onCurrentPageSelected, testID], [
items,
isHeaderReady,
renderHeader,
currentPage,
onCurrentPageSelected,
testID,
],
) )
const onPageSelectedInner = React.useCallback( const onPageSelectedInner = React.useCallback(
@@ -80,8 +89,14 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
{toArray(children) {toArray(children)
.filter(Boolean) .filter(Boolean)
.map((child, i) => { .map((child, i) => {
const isReady = isHeaderReady
return ( return (
<View key={i} collapsable={false}> <View
key={i}
collapsable={false}
style={{
display: isReady ? undefined : 'none',
}}>
<PagerItem isFocused={i === currentPage} renderTab={child} /> <PagerItem isFocused={i === currentPage} renderTab={child} />
</View> </View>
) )
@@ -94,6 +109,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
let PagerTabBar = ({ let PagerTabBar = ({
currentPage, currentPage,
items, items,
isHeaderReady,
testID, testID,
renderHeader, renderHeader,
onCurrentPageSelected, onCurrentPageSelected,
@@ -104,6 +120,7 @@ let PagerTabBar = ({
items: string[] items: string[]
testID?: string testID?: string
renderHeader?: () => JSX.Element renderHeader?: () => JSX.Element
isHeaderReady: boolean
onCurrentPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void
onSelect?: (index: number) => void onSelect?: (index: number) => void
tabBarAnchor?: JSX.Element | null | undefined tabBarAnchor?: JSX.Element | null | undefined
@@ -112,7 +129,12 @@ let PagerTabBar = ({
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
return ( return (
<> <>
<View style={[!isMobile && styles.headerContainerDesktop, pal.border]}> <View
style={[
!isMobile && styles.headerContainerDesktop,
pal.border,
!isHeaderReady && styles.loadingHeader,
]}>
{renderHeader?.()} {renderHeader?.()}
</View> </View>
{tabBarAnchor} {tabBarAnchor}
@@ -123,6 +145,9 @@ let PagerTabBar = ({
? styles.tabBarContainerMobile ? styles.tabBarContainerMobile
: styles.tabBarContainerDesktop, : styles.tabBarContainerDesktop,
pal.border, pal.border,
{
display: isHeaderReady ? undefined : 'none',
},
]}> ]}>
<TabBar <TabBar
testID={testID} testID={testID}
@@ -183,6 +208,9 @@ const styles = StyleSheet.create({
paddingLeft: 14, paddingLeft: 14,
paddingRight: 14, paddingRight: 14,
}, },
loadingHeader: {
borderColor: 'transparent',
},
}) })
function toArray<T>(v: T | T[]): T[] { function toArray<T>(v: T | T[]): T[] {
@@ -0,0 +1,154 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {useNavigation} from '@react-navigation/native'
import {AppBskyActorDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {logger} from '#/logger'
import {Text} from 'view/com/util/text/Text'
import * as Toast from 'view/com/util/Toast'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Shadow, useProfileShadow} from 'state/cache/profile-shadow'
import {track} from 'lib/analytics/analytics'
import {
useProfileFollowMutationQueue,
useProfileQuery,
} from 'state/queries/profile'
import {useRequireAuth} from 'state/session'
export function PostThreadFollowBtn({did}: {did: string}) {
const {data: profile, isLoading} = useProfileQuery({did})
// We will never hit this - the profile will always be cached or loaded above
// but it keeps the typechecker happy
if (isLoading || !profile) return null
return <PostThreadFollowBtnLoaded profile={profile} />
}
function PostThreadFollowBtnLoaded({
profile: profileUnshadowed,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const navigation = useNavigation()
const {_} = useLingui()
const pal = usePalette('default')
const palInverted = usePalette('inverted')
const {isTabletOrDesktop} = useWebMediaQueries()
const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> =
useProfileShadow(profileUnshadowed)
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
const requireAuth = useRequireAuth()
const isFollowing = !!profile.viewer?.following
const [wasFollowing, setWasFollowing] = React.useState<boolean>(isFollowing)
// This prevents the button from disappearing as soon as we follow.
const showFollowBtn = React.useMemo(
() => !isFollowing || !wasFollowing,
[isFollowing, wasFollowing],
)
/**
* We want this button to stay visible even after following, so that the user can unfollow if they want.
* However, we need it to disappear after we push to a screen and then come back. We also need it to
* show up if we view the post while following, go to the profile and unfollow, then come back to the
* post.
*
* We want to update wasFollowing both on blur and on focus so that we hit all these cases. On native,
* we could do this only on focus because the transition animation gives us time to not notice the
* sudden rendering of the button. However, on web if we do this, there's an obvious flicker once the
* button renders. So, we update the state in both cases.
*/
React.useEffect(() => {
const updateWasFollowing = () => {
if (wasFollowing !== isFollowing) {
setWasFollowing(isFollowing)
}
}
const unsubscribeFocus = navigation.addListener('focus', updateWasFollowing)
const unsubscribeBlur = navigation.addListener('blur', updateWasFollowing)
return () => {
unsubscribeFocus()
unsubscribeBlur()
}
}, [isFollowing, wasFollowing, navigation])
const onPress = React.useCallback(() => {
if (!isFollowing) {
requireAuth(async () => {
try {
track('ProfileHeader:FollowButtonClicked')
await queueFollow()
} catch (e: any) {
if (e?.name !== 'AbortError') {
logger.error('Failed to follow', {message: String(e)})
Toast.show(_(msg`There was an issue! ${e.toString()}`))
}
}
})
} else {
requireAuth(async () => {
try {
track('ProfileHeader:UnfollowButtonClicked')
await queueUnfollow()
} catch (e: any) {
if (e?.name !== 'AbortError') {
logger.error('Failed to unfollow', {message: String(e)})
Toast.show(_(msg`There was an issue! ${e.toString()}`))
}
}
})
}
}, [isFollowing, requireAuth, queueFollow, _, queueUnfollow])
if (!showFollowBtn) return null
return (
<View style={{width: isTabletOrDesktop ? 130 : 120}}>
<View style={styles.btnOuter}>
<TouchableOpacity
testID="followBtn"
onPress={onPress}
style={[styles.btn, !isFollowing ? palInverted.view : pal.viewLight]}
accessibilityRole="button"
accessibilityLabel={_(msg`Follow ${profile.handle}`)}
accessibilityHint={_(
msg`Shows posts from ${profile.handle} in your feed`,
)}>
{isTabletOrDesktop && (
<FontAwesomeIcon
icon={!isFollowing ? 'plus' : 'check'}
style={[!isFollowing ? palInverted.text : pal.text, s.mr5]}
/>
)}
<Text
type="button"
style={[!isFollowing ? palInverted.text : pal.text, s.bold]}
numberOfLines={1}>
{!isFollowing ? <Trans>Follow</Trans> : <Trans>Following</Trans>}
</Text>
</TouchableOpacity>
</View>
</View>
)
}
const styles = StyleSheet.create({
btnOuter: {
marginLeft: 'auto',
},
btn: {
flexDirection: 'row',
borderRadius: 50,
paddingVertical: 8,
paddingHorizontal: 14,
},
})
+101 -150
View File
@@ -9,6 +9,7 @@ import {
} from '@atproto/api' } from '@atproto/api'
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn'
import {Link, TextLink} from '../util/Link' import {Link, TextLink} from '../util/Link'
import {RichText} from '../util/text/RichText' import {RichText} from '../util/text/RichText'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
@@ -23,7 +24,6 @@ import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
import {PostMeta} from '../util/PostMeta' import {PostMeta} from '../util/PostMeta'
import {PostEmbeds} from '../util/post-embeds' import {PostEmbeds} from '../util/post-embeds'
import {PostCtrls} from '../util/post-ctrls/PostCtrls' import {PostCtrls} from '../util/post-ctrls/PostCtrls'
import {PostDropdownBtn} from '../util/forms/PostDropdownBtn'
import {PostHider} from '../util/moderation/PostHider' import {PostHider} from '../util/moderation/PostHider'
import {ContentHider} from '../util/moderation/ContentHider' import {ContentHider} from '../util/moderation/ContentHider'
import {PostAlerts} from '../util/moderation/PostAlerts' import {PostAlerts} from '../util/moderation/PostAlerts'
@@ -31,7 +31,6 @@ import {PostSandboxWarning} from '../util/PostSandboxWarning'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {formatCount} from '../util/numeric/format' import {formatCount} from '../util/numeric/format'
import {TimeElapsed} from 'view/com/util/TimeElapsed'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {MAX_POST_LINES} from 'lib/constants' import {MAX_POST_LINES} from 'lib/constants'
@@ -43,7 +42,7 @@ import {useModerationOpts} from '#/state/queries/preferences'
import {useOpenLink} from '#/state/preferences/in-app-browser' import {useOpenLink} from '#/state/preferences/in-app-browser'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {ThreadPost} from '#/state/queries/post-thread' import {ThreadPost} from '#/state/queries/post-thread'
import {useSession} from '#/state/session' import {useSession} from 'state/session'
import {WhoCanReply} from '../threadgate/WhoCanReply' import {WhoCanReply} from '../threadgate/WhoCanReply'
export function PostThreadItem({ export function PostThreadItem({
@@ -115,7 +114,6 @@ export function PostThreadItem({
} }
function PostThreadItemDeleted() { function PostThreadItemDeleted() {
const styles = useStyles()
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
<View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}> <View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}>
@@ -162,11 +160,10 @@ let PostThreadItemLoaded = ({
const {_} = useLingui() const {_} = useLingui()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const {currentAccount} = useSession()
const [limitLines, setLimitLines] = React.useState( const [limitLines, setLimitLines] = React.useState(
() => countLines(richText?.text) >= MAX_POST_LINES, () => countLines(richText?.text) >= MAX_POST_LINES,
) )
const styles = useStyles() const {currentAccount} = useSession()
const hasEngagement = post.likeCount || post.repostCount const hasEngagement = post.likeCount || post.repostCount
const rootUri = record.reply?.root?.uri || post.uri const rootUri = record.reply?.root?.uri || post.uri
@@ -188,9 +185,6 @@ let PostThreadItemLoaded = ({
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by') return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
}, [post.uri, post.author]) }, [post.uri, post.author])
const repostsTitle = _(msg`Reposts of this post`) const repostsTitle = _(msg`Reposts of this post`)
const isModeratedPost =
moderation.decisions.post.cause?.type === 'label' &&
moderation.decisions.post.cause.label.src !== currentAccount?.did
const translatorUrl = getTranslatorLink( const translatorUrl = getTranslatorLink(
record?.text || '', record?.text || '',
@@ -255,7 +249,7 @@ let PostThreadItemLoaded = ({
style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]} style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
accessible={false}> accessible={false}>
<PostSandboxWarning /> <PostSandboxWarning />
<View style={styles.layout}> <View style={[styles.layout]}>
<View style={[styles.layoutAvi, {paddingBottom: 8}]}> <View style={[styles.layoutAvi, {paddingBottom: 8}]}>
<PreviewableUserAvatar <PreviewableUserAvatar
size={42} size={42}
@@ -268,33 +262,18 @@ let PostThreadItemLoaded = ({
<View style={styles.layoutContent}> <View style={styles.layoutContent}>
<View <View
style={[styles.meta, styles.metaExpandedLine1, {zIndex: 1}]}> style={[styles.meta, styles.metaExpandedLine1, {zIndex: 1}]}>
<View style={[s.flexRow]}> <Link style={s.flex1} href={authorHref} title={authorTitle}>
<Link <Text
style={styles.metaItem} type="xl-bold"
href={authorHref} style={[pal.text]}
title={authorTitle}> numberOfLines={1}
<Text lineHeight={1.2}>
type="xl-bold" {sanitizeDisplayName(
style={[pal.text]} post.author.displayName ||
numberOfLines={1} sanitizeHandle(post.author.handle),
lineHeight={1.2}>
{sanitizeDisplayName(
post.author.displayName ||
sanitizeHandle(post.author.handle),
)}
</Text>
</Link>
<TimeElapsed timestamp={post.indexedAt}>
{({timeElapsed}) => (
<Text
type="md"
style={[styles.metaItem, pal.textLight]}
title={niceDate(post.indexedAt)}>
&middot;&nbsp;{timeElapsed}
</Text>
)} )}
</TimeElapsed> </Text>
</View> </Link>
</View> </View>
<View style={styles.meta}> <View style={styles.meta}>
{isAuthorMuted && ( {isAuthorMuted && (
@@ -321,33 +300,16 @@ let PostThreadItemLoaded = ({
</Text> </Text>
</View> </View>
)} )}
<Link <Link style={s.flex1} href={authorHref} title={authorTitle}>
style={styles.metaItem}
href={authorHref}
title={authorTitle}>
<Text type="md" style={[pal.textLight]} numberOfLines={1}> <Text type="md" style={[pal.textLight]} numberOfLines={1}>
{sanitizeHandle(post.author.handle, '@')} {sanitizeHandle(post.author.handle, '@')}
</Text> </Text>
</Link> </Link>
</View> </View>
</View> </View>
<PostDropdownBtn {currentAccount?.did !== post.author.did && (
testID="postDropdownBtn" <PostThreadFollowBtn did={post.author.did} />
postAuthor={post.author} )}
postCid={post.cid}
postUri={post.uri}
record={record}
richText={richText}
showAppealLabelItem={
post.author.did === currentAccount?.did && isModeratedPost
}
style={{
paddingVertical: 6,
paddingHorizontal: 10,
marginLeft: 'auto',
width: 40,
}}
/>
</View> </View>
<View style={[s.pl10, s.pr10, s.pb10]}> <View style={[s.pl10, s.pr10, s.pb10]}>
<ContentHider <ContentHider
@@ -437,7 +399,7 @@ let PostThreadItemLoaded = ({
) : ( ) : (
<></> <></>
)} )}
<View style={[s.pl10, s.pb5]}> <View style={[s.pl10, s.pr10, s.pb5]}>
<PostCtrls <PostCtrls
big big
post={post} post={post}
@@ -649,7 +611,6 @@ function PostOuterWrapper({
}>) { }>) {
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
const styles = useStyles()
if (treeView && depth > 0) { if (treeView && depth > 0) {
return ( return (
<View <View
@@ -726,94 +687,84 @@ function ExpandedPostDetails({
) )
} }
const useStyles = () => { const styles = StyleSheet.create({
const {isDesktop} = useWebMediaQueries() outer: {
return StyleSheet.create({ borderTopWidth: 1,
outer: { paddingLeft: 8,
borderTopWidth: 1, },
paddingLeft: 8, outerHighlighted: {
}, paddingTop: 16,
outerHighlighted: { paddingLeft: 8,
paddingTop: 16, paddingRight: 8,
paddingLeft: 8, },
paddingRight: 8, noTopBorder: {
}, borderTopWidth: 0,
noTopBorder: { },
borderTopWidth: 0, layout: {
}, flexDirection: 'row',
layout: { paddingHorizontal: 8,
flexDirection: 'row', },
gap: 10, layoutAvi: {},
paddingLeft: 8, layoutContent: {
}, flex: 1,
layoutAvi: {}, marginLeft: 10,
layoutContent: { },
flex: 1, meta: {
paddingRight: 10, flexDirection: 'row',
}, paddingVertical: 2,
meta: { },
flexDirection: 'row', metaExpandedLine1: {
paddingTop: 2, paddingVertical: 0,
paddingBottom: 2, },
}, alert: {
metaExpandedLine1: { marginBottom: 6,
paddingTop: 0, },
paddingBottom: 0, postTextContainer: {
}, flexDirection: 'row',
metaItem: { alignItems: 'center',
paddingRight: 5, flexWrap: 'wrap',
maxWidth: isDesktop ? 380 : 220, paddingBottom: 4,
}, paddingRight: 10,
alert: { },
marginBottom: 6, postTextLargeContainer: {
}, paddingHorizontal: 0,
postTextContainer: { paddingRight: 0,
flexDirection: 'row', paddingBottom: 10,
alignItems: 'center', },
flexWrap: 'wrap', translateLink: {
paddingBottom: 4, marginBottom: 6,
paddingRight: 10, },
}, contentHider: {
postTextLargeContainer: { marginBottom: 6,
paddingHorizontal: 0, },
paddingRight: 0, contentHiderChild: {
paddingBottom: 10, marginTop: 6,
}, },
translateLink: { expandedInfo: {
marginBottom: 6, flexDirection: 'row',
}, padding: 10,
contentHider: { borderTopWidth: 1,
marginBottom: 6, borderBottomWidth: 1,
}, marginTop: 5,
contentHiderChild: { marginBottom: 15,
marginTop: 6, },
}, expandedInfoItem: {
expandedInfo: { marginRight: 10,
flexDirection: 'row', },
padding: 10, loadMore: {
borderTopWidth: 1, flexDirection: 'row',
borderBottomWidth: 1, alignItems: 'center',
marginTop: 5, justifyContent: 'flex-start',
marginBottom: 15, gap: 4,
}, paddingHorizontal: 20,
expandedInfoItem: { },
marginRight: 10, replyLine: {
}, width: 2,
loadMore: { marginLeft: 'auto',
flexDirection: 'row', marginRight: 'auto',
alignItems: 'center', },
justifyContent: 'flex-start', cursor: {
gap: 4, // @ts-ignore web only
paddingHorizontal: 20, cursor: 'pointer',
}, },
replyLine: { })
width: 2,
marginLeft: 'auto',
marginRight: 'auto',
},
cursor: {
// @ts-ignore web only
cursor: 'pointer',
},
})
}
+52 -91
View File
@@ -1,4 +1,4 @@
import React, {memo} from 'react' import React, {memo, useMemo} from 'react'
import { import {
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
@@ -10,7 +10,8 @@ import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import { import {
AppBskyActorDefs, AppBskyActorDefs,
ProfileModeration, ModerationOpts,
moderateProfile,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
@@ -43,79 +44,57 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {BACK_HITSLOP} from 'lib/constants' import {BACK_HITSLOP} from 'lib/constants'
import {isInvalidHandle} from 'lib/strings/handles' import {isInvalidHandle, sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {toShareUrl} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {shareUrl} from 'lib/sharing' import {shareUrl} from 'lib/sharing'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useSession, getAgent} from '#/state/session' import {useSession} from '#/state/session'
import {Shadow} from '#/state/cache/types' import {Shadow} from '#/state/cache/types'
import {useRequireAuth} from '#/state/session' import {useRequireAuth} from '#/state/session'
import {LabelInfo} from '../util/moderation/LabelInfo' import {LabelInfo} from '../util/moderation/LabelInfo'
import {useProfileShadow} from 'state/cache/profile-shadow'
interface Props { let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> | null
moderation: ProfileModeration | null
hideBackButton?: boolean
isProfilePreview?: boolean
}
export function ProfileHeader({
profile,
moderation,
hideBackButton = false,
isProfilePreview,
}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
return (
// loading <View style={pal.view}>
// = <LoadingPlaceholder width="100%" height={150} style={{borderRadius: 0}} />
if (!profile || !moderation) { <View
return ( style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
<View style={pal.view}> <LoadingPlaceholder width={80} height={80} style={styles.br40} />
<LoadingPlaceholder width="100%" height={153} /> </View>
<View <View style={styles.content}>
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> <View style={[styles.buttonsLine]}>
<LoadingPlaceholder width={80} height={80} style={styles.br40} /> <LoadingPlaceholder width={167} height={31} style={styles.br50} />
</View>
<View style={styles.content}>
<View style={[styles.buttonsLine]}>
<LoadingPlaceholder width={167} height={31} style={styles.br50} />
</View>
</View> </View>
</View> </View>
) </View>
}
// loaded
// =
return (
<ProfileHeaderLoaded
profile={profile}
moderation={moderation}
hideBackButton={hideBackButton}
isProfilePreview={isProfilePreview}
/>
) )
} }
ProfileHeaderLoading = memo(ProfileHeaderLoading)
export {ProfileHeaderLoading}
interface LoadedProps { interface Props {
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile: AppBskyActorDefs.ProfileViewDetailed
moderation: ProfileModeration descriptionRT: RichTextAPI | null
moderationOpts: ModerationOpts
hideBackButton?: boolean hideBackButton?: boolean
isProfilePreview?: boolean isPlaceholderProfile?: boolean
} }
let ProfileHeaderLoaded = ({ let ProfileHeader = ({
profile, profile: profileUnshadowed,
moderation, descriptionRT,
moderationOpts,
hideBackButton = false, hideBackButton = false,
isProfilePreview, isPlaceholderProfile,
}: LoadedProps): React.ReactNode => { }: Props): React.ReactNode => {
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
useProfileShadow(profileUnshadowed)
const pal = usePalette('default') const pal = usePalette('default')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
@@ -132,37 +111,10 @@ let ProfileHeaderLoaded = ({
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const moderation = useMemo(
/* () => moderateProfile(profile, moderationOpts),
* BEGIN handle bio facet resolution [profile, moderationOpts],
*/
// should be undefined on first render to trigger a resolution
const prevProfileDescription = React.useRef<string | undefined>()
const [descriptionRT, setDescriptionRT] = React.useState<
RichTextAPI | undefined
>(
profile.description
? new RichTextAPI({text: profile.description})
: undefined,
) )
React.useEffect(() => {
async function resolveRTFacets() {
// new each time
const rt = new RichTextAPI({text: profile.description || ''})
await rt.detectFacets(getAgent())
// replace existing RT instance
setDescriptionRT(rt)
}
if (profile.description !== prevProfileDescription.current) {
// update prev immediately
prevProfileDescription.current = profile.description
resolveRTFacets()
}
}, [profile.description, setDescriptionRT])
/*
* END handle bio facet resolution
*/
const invalidateProfileQuery = React.useCallback(() => { const invalidateProfileQuery = React.useCallback(() => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
@@ -443,9 +395,17 @@ let ProfileHeaderLoaded = ({
const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower') const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower')
return ( return (
<View style={pal.view} pointerEvents="box-none"> <View style={[pal.view]} pointerEvents="box-none">
<View pointerEvents="none"> <View pointerEvents="none">
<UserBanner banner={profile.banner} moderation={moderation.avatar} /> {isPlaceholderProfile ? (
<LoadingPlaceholder
width="100%"
height={150}
style={{borderRadius: 0}}
/>
) : (
<UserBanner banner={profile.banner} moderation={moderation.avatar} />
)}
</View> </View>
<View style={styles.content} pointerEvents="box-none"> <View style={styles.content} pointerEvents="box-none">
<View style={[styles.buttonsLine]} pointerEvents="box-none"> <View style={[styles.buttonsLine]} pointerEvents="box-none">
@@ -479,7 +439,7 @@ let ProfileHeaderLoaded = ({
) )
) : !profile.viewer?.blockedBy ? ( ) : !profile.viewer?.blockedBy ? (
<> <>
{!isProfilePreview && hasSession && ( {hasSession && (
<TouchableOpacity <TouchableOpacity
testID="suggestedFollowsBtn" testID="suggestedFollowsBtn"
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)} onPress={() => setShowSuggestedFollows(!showSuggestedFollows)}
@@ -598,7 +558,7 @@ let ProfileHeaderLoaded = ({
{invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`} {invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
</ThemedText> </ThemedText>
</View> </View>
{!blockHide && ( {!isPlaceholderProfile && !blockHide && (
<> <>
<View style={styles.metricsLine} pointerEvents="box-none"> <View style={styles.metricsLine} pointerEvents="box-none">
<Link <Link
@@ -649,7 +609,7 @@ let ProfileHeaderLoaded = ({
</Text> </Text>
</View> </View>
{descriptionRT && !moderation.profile.blur ? ( {descriptionRT && !moderation.profile.blur ? (
<View pointerEvents={isNative ? 'auto' : 'none'}> <View pointerEvents="auto">
<RichText <RichText
testID="profileHeaderDescription" testID="profileHeaderDescription"
style={[styles.description, pal.text]} style={[styles.description, pal.text]}
@@ -667,7 +627,7 @@ let ProfileHeaderLoaded = ({
<ProfileHeaderModCard /> <ProfileHeaderModCard />
</View> </View>
{!isProfilePreview && showSuggestedFollows && ( {showSuggestedFollows && (
<ProfileHeaderSuggestedFollows <ProfileHeaderSuggestedFollows
actorDid={profile.did} actorDid={profile.did}
requestDismiss={() => { requestDismiss={() => {
@@ -714,7 +674,8 @@ let ProfileHeaderLoaded = ({
</View> </View>
) )
} }
ProfileHeaderLoaded = memo(ProfileHeaderLoaded) ProfileHeader = memo(ProfileHeader)
export {ProfileHeader}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
banner: { banner: {
+4 -1
View File
@@ -123,6 +123,7 @@ let UserAvatar = ({
usePlainRNImage = false, usePlainRNImage = false,
}: UserAvatarProps): React.ReactNode => { }: UserAvatarProps): React.ReactNode => {
const pal = usePalette('default') const pal = usePalette('default')
const backgroundColor = pal.colors.backgroundLight
const aviStyle = useMemo(() => { const aviStyle = useMemo(() => {
if (type === 'algo' || type === 'list') { if (type === 'algo' || type === 'list') {
@@ -130,14 +131,16 @@ let UserAvatar = ({
width: size, width: size,
height: size, height: size,
borderRadius: size > 32 ? 8 : 3, borderRadius: size > 32 ? 8 : 3,
backgroundColor,
} }
} }
return { return {
width: size, width: size,
height: size, height: size,
borderRadius: Math.floor(size / 2), borderRadius: Math.floor(size / 2),
backgroundColor,
} }
}, [type, size]) }, [type, size, backgroundColor])
const alert = useMemo(() => { const alert = useMemo(() => {
if (!moderation?.alert) { if (!moderation?.alert) {
+8 -3
View File
@@ -3,7 +3,10 @@ import {StyleSheet, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ModerationUI} from '@atproto/api' import {ModerationUI} from '@atproto/api'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {colors} from 'lib/styles' import {colors} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext'
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
import { import {
usePhotoLibraryPermission, usePhotoLibraryPermission,
@@ -13,8 +16,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb, isAndroid} from 'platform/detection' import {isWeb, isAndroid} from 'platform/detection'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
import {NativeDropdown, DropdownItem} from './forms/NativeDropdown' import {NativeDropdown, DropdownItem} from './forms/NativeDropdown'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
export function UserBanner({ export function UserBanner({
banner, banner,
@@ -26,6 +27,7 @@ export function UserBanner({
onSelectNewBanner?: (img: RNImage | null) => void onSelectNewBanner?: (img: RNImage | null) => void
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {requestCameraAccessIfNeeded} = useCameraPermission() const {requestCameraAccessIfNeeded} = useCameraPermission()
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
@@ -142,7 +144,10 @@ export function UserBanner({
!((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( !((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
<Image <Image
testID="userBannerImage" testID="userBannerImage"
style={styles.bannerImage} style={[
styles.bannerImage,
{backgroundColor: theme.palette.default.backgroundLight},
]}
resizeMode="cover" resizeMode="cover"
source={{uri: banner}} source={{uri: banner}}
blurRadius={moderation?.blur ? 100 : 0} blurRadius={moderation?.blur ? 100 : 0}
+12 -32
View File
@@ -1,9 +1,8 @@
import React from 'react' import React from 'react'
import {Pressable, StyleProp, ViewStyle} from 'react-native' import {StyleProp, ViewStyle} from 'react-native'
import {Link} from './Link' import {Link} from './Link'
import {isAndroid, isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {useModalControls} from '#/state/modals'
import {usePrefetchProfileQuery} from '#/state/queries/profile' import {usePrefetchProfileQuery} from '#/state/queries/profile'
interface UserPreviewLinkProps { interface UserPreviewLinkProps {
@@ -14,38 +13,19 @@ interface UserPreviewLinkProps {
export function UserPreviewLink( export function UserPreviewLink(
props: React.PropsWithChildren<UserPreviewLinkProps>, props: React.PropsWithChildren<UserPreviewLinkProps>,
) { ) {
const {openModal} = useModalControls()
const prefetchProfileQuery = usePrefetchProfileQuery() const prefetchProfileQuery = usePrefetchProfileQuery()
if (isWeb || isAndroid) {
return (
<Link
onPointerEnter={() => {
if (isWeb) {
prefetchProfileQuery(props.did)
}
}}
href={makeProfileLink(props)}
title={props.handle}
asAnchor
style={props.style}>
{props.children}
</Link>
)
}
return ( return (
<Pressable <Link
onPress={() => onPointerEnter={() => {
openModal({ if (isWeb) {
name: 'profile-preview', prefetchProfileQuery(props.did)
did: props.did, }
}) }}
} href={makeProfileLink(props)}
accessibilityRole="button" title={props.handle}
accessibilityLabel={props.handle} asAnchor
accessibilityHint=""
style={props.style}> style={props.style}>
{props.children} {props.children}
</Pressable> </Link>
) )
} }
+53 -45
View File
@@ -11,6 +11,8 @@ import {Button} from '../forms/Button'
import {CenteredView} from '../Views' import {CenteredView} from '../Views'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export function ErrorScreen({ export function ErrorScreen({
title, title,
@@ -18,66 +20,72 @@ export function ErrorScreen({
details, details,
onPressTryAgain, onPressTryAgain,
testID, testID,
showHeader,
}: { }: {
title: string title: string
message: string message: string
details?: string details?: string
onPressTryAgain?: () => void onPressTryAgain?: () => void
testID?: string testID?: string
showHeader?: boolean
}) { }) {
const theme = useTheme() const theme = useTheme()
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
return ( return (
<CenteredView testID={testID} style={[styles.outer, pal.view]}> <>
<View style={styles.errorIconContainer}> {showHeader && isMobile && <ViewHeader title="Error" showBorder />}
<View <CenteredView testID={testID} style={[styles.outer, pal.view]}>
style={[ <View style={styles.errorIconContainer}>
styles.errorIcon, <View
{backgroundColor: theme.palette.inverted.background}, style={[
]}> styles.errorIcon,
<FontAwesomeIcon {backgroundColor: theme.palette.inverted.background},
icon="exclamation" ]}>
style={pal.textInverted as FontAwesomeIconStyle}
size={24}
/>
</View>
</View>
<Text type="title-lg" style={[styles.title, pal.text]}>
{title}
</Text>
<Text style={[styles.message, pal.text]}>{message}</Text>
{details && (
<Text
testID={`${testID}-details`}
style={[styles.details, pal.text, pal.viewLight]}>
{details}
</Text>
)}
{onPressTryAgain && (
<View style={styles.btnContainer}>
<Button
testID="errorScreenTryAgainButton"
type="default"
style={[styles.btn]}
onPress={onPressTryAgain}
accessibilityLabel={_(msg`Retry`)}
accessibilityHint={_(
msg`Retries the last action, which errored out`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="arrows-rotate" icon="exclamation"
style={pal.link as FontAwesomeIconStyle} style={pal.textInverted as FontAwesomeIconStyle}
size={16} size={24}
/> />
<Text type="button" style={[styles.btnText, pal.link]}> </View>
<Trans context="action">Try again</Trans>
</Text>
</Button>
</View> </View>
)} <Text type="title-lg" style={[styles.title, pal.text]}>
</CenteredView> {title}
</Text>
<Text style={[styles.message, pal.text]}>{message}</Text>
{details && (
<Text
testID={`${testID}-details`}
style={[styles.details, pal.text, pal.viewLight]}>
{details}
</Text>
)}
{onPressTryAgain && (
<View style={styles.btnContainer}>
<Button
testID="errorScreenTryAgainButton"
type="default"
style={[styles.btn]}
onPress={onPressTryAgain}
accessibilityLabel={_(msg`Retry`)}
accessibilityHint={_(
msg`Retries the last action, which errored out`,
)}>
<FontAwesomeIcon
icon="arrows-rotate"
style={pal.link as FontAwesomeIconStyle}
size={16}
/>
<Text type="button" style={[styles.btnText, pal.link]}>
<Trans context="action">Try again</Trans>
</Text>
</Button>
</View>
)}
</CenteredView>
</>
) )
} }
+44 -15
View File
@@ -9,6 +9,7 @@ import {
import { import {
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyFeedPost, AppBskyFeedPost,
AtUri,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {Text} from '../text/Text' import {Text} from '../text/Text'
@@ -30,6 +31,10 @@ import {Shadow} from '#/state/cache/types'
import {useRequireAuth} from '#/state/session' import {useRequireAuth} from '#/state/session'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
import {toShareUrl} from 'lib/strings/url-helpers'
import {shareUrl} from 'lib/sharing'
import {makeProfileLink} from 'lib/routes/links'
let PostCtrls = ({ let PostCtrls = ({
big, big,
@@ -116,11 +121,18 @@ let PostCtrls = ({
closeModal, closeModal,
]) ])
const onShare = useCallback(() => {
const urip = new AtUri(post.uri)
const href = makeProfileLink(post.author, 'post', urip.rkey)
const url = toShareUrl(href)
shareUrl(url)
}, [post.uri, post.author])
return ( return (
<View style={[styles.ctrls, style]}> <View style={[styles.ctrls, style]}>
<View <View
style={[ style={[
styles.ctrl, big ? styles.ctrlBig : styles.ctrl,
post.viewer?.replyDisabled ? {opacity: 0.5} : undefined, post.viewer?.replyDisabled ? {opacity: 0.5} : undefined,
]}> ]}>
<TouchableOpacity <TouchableOpacity
@@ -149,7 +161,7 @@ let PostCtrls = ({
) : undefined} ) : undefined}
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.ctrl}> <View style={big ? styles.ctrlBig : styles.ctrl}>
<RepostButton <RepostButton
big={big} big={big}
isReposted={!!post.viewer?.repost} isReposted={!!post.viewer?.repost}
@@ -158,7 +170,7 @@ let PostCtrls = ({
onQuote={onQuote} onQuote={onQuote}
/> />
</View> </View>
<View style={styles.ctrl}> <View style={big ? styles.ctrlBig : styles.ctrl}>
<TouchableOpacity <TouchableOpacity
testID="likeBtn" testID="likeBtn"
style={[styles.btn, !big && styles.btnPad]} style={[styles.btn, !big && styles.btnPad]}
@@ -193,20 +205,34 @@ let PostCtrls = ({
) : undefined} ) : undefined}
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{big ? undefined : ( {big && (
<View style={styles.ctrl}> <View style={styles.ctrlBig}>
<PostDropdownBtn <TouchableOpacity
testID="postDropdownBtn" testID="likeBtn"
postAuthor={post.author} style={[styles.btn]}
postCid={post.cid} onPress={onShare}
postUri={post.uri} accessibilityRole="button"
record={record} accessibilityLabel={`${
richText={richText} post.viewer?.like ? _(msg`Unlike`) : _(msg`Like`)
showAppealLabelItem={showAppealLabelItem} } (${post.likeCount} ${pluralize(post.likeCount || 0, 'like')})`}
style={styles.btnPad} accessibilityHint=""
/> hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
<ArrowOutOfBox style={[defaultCtrlColor, styles.mt1]} width={22} />
</TouchableOpacity>
</View> </View>
)} )}
<View style={big ? styles.ctrlBig : styles.ctrl}>
<PostDropdownBtn
testID="postDropdownBtn"
postAuthor={post.author}
postCid={post.cid}
postUri={post.uri}
record={record}
richText={richText}
showAppealLabelItem={showAppealLabelItem}
style={styles.btnPad}
/>
</View>
</View> </View>
) )
} }
@@ -223,6 +249,9 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
alignItems: 'flex-start', alignItems: 'flex-start',
}, },
ctrlBig: {
alignItems: 'center',
},
btn: { btn: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
+2 -2
View File
@@ -37,8 +37,8 @@ export const CommunityGuidelinesScreen = (_props: Props) => {
The Community Guidelines have been moved to{' '} The Community Guidelines have been moved to{' '}
<TextLink <TextLink
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines" href="https://bsky.social/about/support/community-guidelines"
text="blueskyweb.xyz/support/community-guidelines" text="bsky.social/about/support/community-guidelines"
/> />
</Trans> </Trans>
</Text> </Text>
+2 -2
View File
@@ -34,8 +34,8 @@ export const CopyrightPolicyScreen = (_props: Props) => {
The Copyright Policy has been moved to{' '} The Copyright Policy has been moved to{' '}
<TextLink <TextLink
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines" href="https://bsky.social/about/support/copyright"
text="blueskyweb.xyz/support/community-guidelines" text="bsky.social/about/support/copyright"
/> />
</Trans> </Trans>
</Text> </Text>
+18 -13
View File
@@ -7,7 +7,7 @@ import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed' import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState' import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
import {FeedsTabBar} from '../com/pager/FeedsTabBar' import {FeedsTabBar} from '../com/pager/FeedsTabBar'
import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager'
import {FeedPage} from 'view/com/feeds/FeedPage' import {FeedPage} from 'view/com/feeds/FeedPage'
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA' import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell' import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
@@ -16,25 +16,19 @@ import {usePinnedFeedsInfos, FeedSourceInfo} from '#/state/queries/feed'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
import * as persisted from '#/state/persisted'
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
export function HomeScreen(props: Props) { export function HomeScreen(props: Props) {
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} = const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
usePinnedFeedsInfos() usePinnedFeedsInfos()
const {isDesktop} = useWebMediaQueries()
const [rawInitialFeed] = React.useState<string>(
() => persisted.get('lastSelectedHomeFeed') ?? 'home',
)
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) { if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
return ( return (
<HomeScreenReady <HomeScreenReady
{...props} {...props}
preferences={preferences} preferences={preferences}
pinnedFeedInfos={pinnedFeedInfos} pinnedFeedInfos={pinnedFeedInfos}
rawInitialFeed={isDesktop ? 'home' : rawInitialFeed}
/> />
) )
} else { } else {
@@ -49,11 +43,9 @@ export function HomeScreen(props: Props) {
function HomeScreenReady({ function HomeScreenReady({
preferences, preferences,
pinnedFeedInfos, pinnedFeedInfos,
rawInitialFeed,
}: Props & { }: Props & {
preferences: UsePreferencesQueryResponse preferences: UsePreferencesQueryResponse
pinnedFeedInfos: FeedSourceInfo[] pinnedFeedInfos: FeedSourceInfo[]
rawInitialFeed: string
}) { }) {
const allFeeds = React.useMemo(() => { const allFeeds = React.useMemo(() => {
const feeds: FeedDescriptor[] = [] const feeds: FeedDescriptor[] = []
@@ -68,12 +60,24 @@ function HomeScreenReady({
return feeds return feeds
}, [pinnedFeedInfos]) }, [pinnedFeedInfos])
const [rawSelectedFeed, setSelectedFeed] = const rawSelectedFeed = useSelectedFeed()
React.useState<string>(rawInitialFeed) const setSelectedFeed = useSetSelectedFeed()
const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed as FeedDescriptor) const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed as FeedDescriptor)
const selectedIndex = Math.max(0, maybeFoundIndex) const selectedIndex = Math.max(0, maybeFoundIndex)
const selectedFeed = allFeeds[selectedIndex] const selectedFeed = allFeeds[selectedIndex]
const pagerRef = React.useRef<PagerRef>(null)
const lastPagerReportedIndexRef = React.useRef(selectedIndex)
React.useLayoutEffect(() => {
// Since the pager is not a controlled component, adjust it imperatively
// if the selected index gets out of sync with what it last reported.
// This is supposed to only happen on the web when you use the right nav.
if (selectedIndex !== lastPagerReportedIndexRef.current) {
lastPagerReportedIndexRef.current = selectedIndex
pagerRef.current?.setPage(selectedIndex)
}
}, [selectedIndex])
const {hasSession} = useSession() const {hasSession} = useSession()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
@@ -93,7 +97,7 @@ function HomeScreenReady({
setDrawerSwipeDisabled(index > 0) setDrawerSwipeDisabled(index > 0)
const feed = allFeeds[index] const feed = allFeeds[index]
setSelectedFeed(feed) setSelectedFeed(feed)
persisted.write('lastSelectedHomeFeed', feed) lastPagerReportedIndexRef.current = index
}, },
[setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds], [setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds],
) )
@@ -147,6 +151,7 @@ function HomeScreenReady({
return hasSession ? ( return hasSession ? (
<Pager <Pager
key={allFeeds.join(',')} key={allFeeds.join(',')}
ref={pagerRef}
testID="homeScreen" testID="homeScreen"
initialPage={selectedIndex} initialPage={selectedIndex}
onPageSelected={onPageSelected} onPageSelected={onPageSelected}
+12
View File
@@ -29,6 +29,9 @@ import {listenSoftReset, emitSoftReset} from '#/state/events'
import {truncateAndInvalidate} from '#/state/queries/util' import {truncateAndInvalidate} from '#/state/queries/util'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {FAB} from '../com/util/fab/FAB'
import {ComposeIcon2} from 'lib/icons'
import {useComposerControls} from '#/state/shell/composer'
type Props = NativeStackScreenProps< type Props = NativeStackScreenProps<
NotificationsTabNavigatorParams, NotificationsTabNavigatorParams,
@@ -47,6 +50,7 @@ export function NotificationsScreen({}: Props) {
const unreadApi = useUnreadNotificationsApi() const unreadApi = useUnreadNotificationsApi()
const hasNew = !!unreadNotifs const hasNew = !!unreadNotifs
const isScreenFocused = useIsFocused() const isScreenFocused = useIsFocused()
const {openComposer} = useComposerControls()
// event handlers // event handlers
// = // =
@@ -156,6 +160,14 @@ export function NotificationsScreen({}: Props) {
showIndicator={hasNew} showIndicator={hasNew}
/> />
)} )}
<FAB
testID="composeFAB"
onPress={() => openComposer({})}
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
accessibilityRole="button"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</View> </View>
) )
} }
+2 -2
View File
@@ -34,8 +34,8 @@ export const PrivacyPolicyScreen = (_props: Props) => {
The Privacy Policy has been moved to{' '} The Privacy Policy has been moved to{' '}
<TextLink <TextLink
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy" href="https://bsky.social/about/support/privacy-policy"
text="blueskyweb.xyz/support/privacy-policy" text="bsky.social/about/support/privacy-policy"
/> />
</Trans> </Trans>
</Text> </Text>
+75 -28
View File
@@ -1,7 +1,12 @@
import React, {useMemo} from 'react' import React, {useMemo} from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' import {
AppBskyActorDefs,
moderateProfile,
ModerationOpts,
RichText as RichTextAPI,
} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
@@ -11,7 +16,7 @@ import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
import {Feed} from 'view/com/posts/Feed' import {Feed} from 'view/com/posts/Feed'
import {ProfileLists} from '../com/lists/ProfileLists' import {ProfileLists} from '../com/lists/ProfileLists'
import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens' import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
import {ProfileHeader} from '../com/profile/ProfileHeader' import {ProfileHeader, ProfileHeaderLoading} from '../com/profile/ProfileHeader'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ErrorScreen} from '../com/util/error/ErrorScreen' import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {EmptyState} from '../com/util/EmptyState' import {EmptyState} from '../com/util/EmptyState'
@@ -28,7 +33,7 @@ import {
import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession} from '#/state/session' import {useSession, getAgent} from '#/state/session'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info' import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
@@ -50,6 +55,7 @@ interface SectionRef {
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
export function ProfileScreen({route}: Props) { export function ProfileScreen({route}: Props) {
const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const name = const name =
route.params.name === 'me' ? currentAccount?.did : route.params.name route.params.name === 'me' ? currentAccount?.did : route.params.name
@@ -65,6 +71,7 @@ export function ProfileScreen({route}: Props) {
error: profileError, error: profileError,
refetch: refetchProfile, refetch: refetchProfile,
isLoading: isLoadingProfile, isLoading: isLoadingProfile,
isPlaceholderData: isPlaceholderProfile,
} = useProfileQuery({ } = useProfileQuery({
did: resolvedDid, did: resolvedDid,
}) })
@@ -84,27 +91,23 @@ export function ProfileScreen({route}: Props) {
} }
}, [profile?.viewer?.blockedBy, resolvedDid]) }, [profile?.viewer?.blockedBy, resolvedDid])
if (isLoadingDid || isLoadingProfile || !moderationOpts) { // Most pushes will happen here, since we will have only placeholder data
if (isLoadingDid || isLoadingProfile) {
return ( return (
<CenteredView> <CenteredView>
<ProfileHeader <ProfileHeaderLoading />
profile={null}
moderation={null}
isProfilePreview={true}
/>
</CenteredView> </CenteredView>
) )
} }
if (resolveError || profileError) { if (resolveError || profileError) {
return ( return (
<CenteredView> <ErrorScreen
<ErrorScreen testID="profileErrorScreen"
testID="profileErrorScreen" title={profileError ? _(msg`Not Found`) : _(msg`Oops!`)}
title="Oops!" message={cleanError(resolveError || profileError)}
message={cleanError(resolveError || profileError)} onPressTryAgain={onPressTryAgain}
onPressTryAgain={onPressTryAgain} showHeader
/> />
</CenteredView>
) )
} }
if (profile && moderationOpts) { if (profile && moderationOpts) {
@@ -112,31 +115,33 @@ export function ProfileScreen({route}: Props) {
<ProfileScreenLoaded <ProfileScreenLoaded
profile={profile} profile={profile}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}
isPlaceholderProfile={isPlaceholderProfile}
hideBackButton={!!route.params.hideBackButton} hideBackButton={!!route.params.hideBackButton}
/> />
) )
} }
// should never happen // should never happen
return ( return (
<CenteredView> <ErrorScreen
<ErrorScreen testID="profileErrorScreen"
testID="profileErrorScreen" title="Oops!"
title="Oops!" message="Something went wrong and we're not sure what."
message="Something went wrong and we're not sure what." onPressTryAgain={onPressTryAgain}
onPressTryAgain={onPressTryAgain} showHeader
/> />
</CenteredView>
) )
} }
function ProfileScreenLoaded({ function ProfileScreenLoaded({
profile: profileUnshadowed, profile: profileUnshadowed,
isPlaceholderProfile,
moderationOpts, moderationOpts,
hideBackButton, hideBackButton,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
hideBackButton: boolean hideBackButton: boolean
isPlaceholderProfile: boolean
}) { }) {
const profile = useProfileShadow(profileUnshadowed) const profile = useProfileShadow(profileUnshadowed)
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
@@ -156,6 +161,10 @@ function ProfileScreenLoaded({
useSetTitle(combinedDisplayName(profile)) useSetTitle(combinedDisplayName(profile))
const description = profile.description ?? ''
const hasDescription = description !== ''
const [descriptionRT, isResolvingDescriptionRT] = useRichText(description)
const showPlaceholder = isPlaceholderProfile || isResolvingDescriptionRT
const moderation = useMemo( const moderation = useMemo(
() => moderateProfile(profile, moderationOpts), () => moderateProfile(profile, moderationOpts),
[profile, moderationOpts], [profile, moderationOpts],
@@ -269,11 +278,20 @@ function ProfileScreenLoaded({
return ( return (
<ProfileHeader <ProfileHeader
profile={profile} profile={profile}
moderation={moderation} descriptionRT={hasDescription ? descriptionRT : null}
moderationOpts={moderationOpts}
hideBackButton={hideBackButton} hideBackButton={hideBackButton}
isPlaceholderProfile={showPlaceholder}
/> />
) )
}, [profile, moderation, hideBackButton]) }, [
profile,
descriptionRT,
hasDescription,
moderationOpts,
hideBackButton,
showPlaceholder,
])
return ( return (
<ScreenHider <ScreenHider
@@ -283,7 +301,7 @@ function ProfileScreenLoaded({
moderation={moderation.account}> moderation={moderation.account}>
<PagerWithHeader <PagerWithHeader
testID="profilePager" testID="profilePager"
isHeaderReady={true} isHeaderReady={!showPlaceholder}
items={sectionTitles} items={sectionTitles}
onPageSelected={onPageSelected} onPageSelected={onPageSelected}
onCurrentPageSelected={onCurrentPageSelected} onCurrentPageSelected={onCurrentPageSelected}
@@ -440,6 +458,35 @@ function ProfileEndOfFeed() {
) )
} }
function useRichText(text: string): [RichTextAPI, boolean] {
const [prevText, setPrevText] = React.useState(text)
const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text}))
const [resolvedRT, setResolvedRT] = React.useState<RichTextAPI | null>(null)
if (text !== prevText) {
setPrevText(text)
setRawRT(new RichTextAPI({text}))
setResolvedRT(null)
// This will queue an immediate re-render
}
React.useEffect(() => {
let ignore = false
async function resolveRTFacets() {
// new each time
const resolvedRT = new RichTextAPI({text})
await resolvedRT.detectFacets(getAgent())
if (!ignore) {
setResolvedRT(resolvedRT)
}
}
resolveRTFacets()
return () => {
ignore = true
}
}, [text])
const isResolving = resolvedRT === null
return [resolvedRT ?? rawRT, isResolving]
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flexDirection: 'column', flexDirection: 'column',
+2 -2
View File
@@ -863,13 +863,13 @@ export function SettingsScreen({}: Props) {
<TextLink <TextLink
type="md" type="md"
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/tos" href="https://bsky.social/about/support/tos"
text={_(msg`Terms of Service`)} text={_(msg`Terms of Service`)}
/> />
<TextLink <TextLink
type="md" type="md"
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy" href="https://bsky.social/about/support/privacy-policy"
text={_(msg`Privacy Policy`)} text={_(msg`Privacy Policy`)}
/> />
</View> </View>
+4 -4
View File
@@ -14,19 +14,19 @@ export function Links() {
<View style={[a.gap_md, a.align_start]}> <View style={[a.gap_md, a.align_start]}>
<InlineLink <InlineLink
to="https://blueskyweb.xyz" to="https://bsky.social"
warnOnMismatchingTextChild warnOnMismatchingTextChild
style={[a.text_md]}> style={[a.text_md]}>
External External
</InlineLink> </InlineLink>
<InlineLink to="https://blueskyweb.xyz" style={[a.text_md]}> <InlineLink to="https://bsky.social" style={[a.text_md]}>
<H3>External with custom children</H3> <H3>External with custom children</H3>
</InlineLink> </InlineLink>
<InlineLink <InlineLink
to="https://blueskyweb.xyz" to="https://bsky.social"
warnOnMismatchingTextChild warnOnMismatchingTextChild
style={[a.text_lg]}> style={[a.text_lg]}>
https://blueskyweb.xyz https://bsky.social
</InlineLink> </InlineLink>
<InlineLink <InlineLink
to="https://bsky.app/profile/bsky.app" to="https://bsky.app/profile/bsky.app"
+10 -10
View File
@@ -14,22 +14,22 @@ export function Theming() {
<Text style={[a.font_bold, a.pt_xl, a.px_md]}>theme.atoms.text</Text> <Text style={[a.font_bold, a.pt_xl, a.px_md]}>theme.atoms.text</Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} /> <View style={[a.flex_1, t.atoms.border_contrast_high, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_600, a.px_md]}> <Text style={[a.font_bold, t.atoms.text_contrast_high, a.px_md]}>
theme.atoms.text_contrast_600 theme.atoms.text_contrast_high
</Text> </Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} /> <View style={[a.flex_1, t.atoms.border_contrast_medium, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_500, a.px_md]}> <Text style={[a.font_bold, t.atoms.text_contrast_medium, a.px_md]}>
theme.atoms.text_contrast_500 theme.atoms.text_contrast_medium
</Text> </Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} /> <View style={[a.flex_1, t.atoms.border_contrast_low, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_400, a.px_md]}> <Text style={[a.font_bold, t.atoms.text_contrast_low, a.px_md]}>
theme.atoms.text_contrast_400 theme.atoms.text_contrast_low
</Text> </Text>
<View style={[a.flex_1, t.atoms.border_contrast, a.border_t]} /> <View style={[a.flex_1, t.atoms.border_contrast_low, a.border_t]} />
<View style={[a.w_full, a.gap_md]}> <View style={[a.w_full, a.gap_md]}>
<View style={[t.atoms.bg, a.justify_center, a.p_md]}> <View style={[t.atoms.bg, a.justify_center, a.p_md]}>
+3 -11
View File
@@ -2,20 +2,12 @@ import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {Text, H1, H2, H3, H4, H5, H6, P} from '#/components/Typography' import {Text} from '#/components/Typography'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
export function Typography() { export function Typography() {
return ( return (
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
<H1>H1 Heading</H1>
<H2>H2 Heading</H2>
<H3>H3 Heading</H3>
<H4>H4 Heading</H4>
<H5>H5 Heading</H5>
<H6>H6 Heading</H6>
<P>P Paragraph</P>
<Text style={[a.text_5xl]}>atoms.text_5xl</Text> <Text style={[a.text_5xl]}>atoms.text_5xl</Text>
<Text style={[a.text_4xl]}>atoms.text_4xl</Text> <Text style={[a.text_4xl]}>atoms.text_4xl</Text>
<Text style={[a.text_3xl]}>atoms.text_3xl</Text> <Text style={[a.text_3xl]}>atoms.text_3xl</Text>
@@ -29,11 +21,11 @@ export function Typography() {
<RichText <RichText
resolveFacets resolveFacets
value={`This is rich text. It can have mentions like @bsky.app or links like https://blueskyweb.xyz`} value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
/> />
<RichText <RichText
resolveFacets resolveFacets
value={`This is rich text. It can have mentions like @bsky.app or links like https://blueskyweb.xyz`} value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
style={[a.text_xl]} style={[a.text_xl]}
/> />
</View> </View>
+2 -2
View File
@@ -33,8 +33,8 @@ export const TermsOfServiceScreen = (_props: Props) => {
<Trans>The Terms of Service have been moved to</Trans>{' '} <Trans>The Terms of Service have been moved to</Trans>{' '}
<TextLink <TextLink
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/tos" href="https://bsky.social/about/support/tos"
text="blueskyweb.xyz/support/tos" text="bsky.social/about/support/tos"
/> />
</Text> </Text>
</View> </View>
+2 -2
View File
@@ -255,13 +255,13 @@ let DrawerContent = ({}: {}): React.ReactNode => {
<TextLink <TextLink
type="md" type="md"
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/tos" href="https://bsky.social/about/support/tos"
text={_(msg`Terms of Service`)} text={_(msg`Terms of Service`)}
/> />
<TextLink <TextLink
type="md" type="md"
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy" href="https://bsky.social/about/support/privacy-policy"
text={_(msg`Privacy Policy`)} text={_(msg`Privacy Policy`)}
/> />
</View> </View>
+40 -27
View File
@@ -1,18 +1,24 @@
import React from 'react' import React from 'react'
import {View, StyleSheet} from 'react-native' import {View, StyleSheet} from 'react-native'
import {useNavigationState} from '@react-navigation/native' import {useNavigationState, useNavigation} from '@react-navigation/native'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {getCurrentRoute} from 'lib/routes/helpers' import {getCurrentRoute} from 'lib/routes/helpers'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {usePinnedFeedsInfos} from '#/state/queries/feed' import {usePinnedFeedsInfos} from '#/state/queries/feed'
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
import {FeedDescriptor} from '#/state/queries/post-feed'
import {NavigationProp} from 'lib/routes/types'
import {emitSoftReset} from '#/state/events'
export function DesktopFeeds() { export function DesktopFeeds() {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {feeds} = usePinnedFeedsInfos() const {feeds: pinnedFeedInfos} = usePinnedFeedsInfos()
const selectedFeed = useSelectedFeed()
const setSelectedFeed = useSetSelectedFeed()
const navigation = useNavigation<NavigationProp>()
const route = useNavigationState(state => { const route = useNavigationState(state => {
if (!state) { if (!state) {
return {name: 'Home'} return {name: 'Home'}
@@ -22,30 +28,34 @@ export function DesktopFeeds() {
return ( return (
<View style={[styles.container, pal.view]}> <View style={[styles.container, pal.view]}>
<FeedItem href="/" title="Following" current={route.name === 'Home'} /> {pinnedFeedInfos.map(feedInfo => {
{feeds const uri = feedInfo.uri
.filter(f => f.displayName !== 'Following') let feed: FeedDescriptor
.map(feed => { if (!uri) {
try { feed = 'home'
const params = route.params as Record<string, string> } else if (uri.includes('app.bsky.feed.generator')) {
const routeName = feed = `feedgen|${uri}`
feed.type === 'feed' ? 'ProfileFeed' : 'ProfileList' } else if (uri.includes('app.bsky.graph.list')) {
return ( feed = `list|${uri}`
<FeedItem } else {
key={feed.uri} return null
href={feed.route.href} }
title={feed.displayName} return (
current={ <FeedItem
route.name === routeName && key={feed}
params.name === feed.route.params.name && href={'/?' + new URLSearchParams([['feed', feed]])}
params.rkey === feed.route.params.rkey title={feedInfo.displayName}
} current={route.name === 'Home' && feed === selectedFeed}
/> onPress={() => {
) setSelectedFeed(feed)
} catch { navigation.navigate('Home')
return null if (feed === selectedFeed) {
} emitSoftReset()
})} }
}}
/>
)
})}
<View style={{paddingTop: 8, paddingBottom: 6}}> <View style={{paddingTop: 8, paddingBottom: 6}}>
<TextLink <TextLink
type="lg" type="lg"
@@ -62,10 +72,12 @@ function FeedItem({
title, title,
href, href,
current, current,
onPress,
}: { }: {
title: string title: string
href: string href: string
current: boolean current: boolean
onPress: () => void
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
@@ -74,6 +86,7 @@ function FeedItem({
type="xl" type="xl"
href={href} href={href}
text={title} text={title}
onPress={onPress}
style={[ style={[
current ? pal.text : pal.textLight, current ? pal.text : pal.textLight,
{letterSpacing: 0.15, fontWeight: current ? '500' : 'normal'}, {letterSpacing: 0.15, fontWeight: current ? '500' : 'normal'},
+1 -1
View File
@@ -334,7 +334,7 @@ export function DesktopLeftNav() {
} }
iconFilled={ iconFilled={
<HashtagIcon <HashtagIcon
strokeWidth={2.5} strokeWidth={4}
style={pal.text as FontAwesomeIconStyle} style={pal.text as FontAwesomeIconStyle}
size={isDesktop ? 24 : 28} size={isDesktop ? 24 : 28}
/> />
+2 -2
View File
@@ -80,7 +80,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
<TextLink <TextLink
type="md" type="md"
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy" href="https://bsky.social/about/support/privacy-policy"
text={_(msg`Privacy`)} text={_(msg`Privacy`)}
/> />
<Text type="md" style={pal.textLight}> <Text type="md" style={pal.textLight}>
@@ -89,7 +89,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
<TextLink <TextLink
type="md" type="md"
style={pal.link} style={pal.link}
href="https://blueskyweb.xyz/support/tos" href="https://bsky.social/about/support/tos"
text={_(msg`Terms`)} text={_(msg`Terms`)}
/> />
<Text type="md" style={pal.textLight}> <Text type="md" style={pal.textLight}>
+4 -5
View File
@@ -1,8 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="%LANG_ISO_CODE%"> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<!-- <!--
This viewport works for phones with notches. This viewport works for phones with notches.
It's optimized for gestures by disabling global zoom. It's optimized for gestures by disabling global zoom.
@@ -234,8 +233,8 @@
width: 80%; width: 80%;
" "
> >
<p>Oh no! It looks like JavaScript is not enabled in your browser.</p> <p lang="en">Oh no! It looks like JavaScript is not enabled in your browser.</p>
<p style="margin: 20px 0;"> <p lang="en" style="margin: 20px 0;">
<button <button
type="submit" type="submit"
style=" style="