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:
@@ -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
|
||||
@@ -6,7 +6,7 @@ Get the app itself:
|
||||
|
||||
- **Web: [bsky.app](https://bsky.app)**
|
||||
- **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
|
||||
|
||||
@@ -21,7 +21,7 @@ The Authenticated Transfer Protocol ("AT Protocol" or "atproto") is a decentrali
|
||||
- [Overview and Guides](https://atproto.com/guides/overview)
|
||||
- [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions
|
||||
- [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.*`.
|
||||
|
||||
|
||||
+3
-1
@@ -19,11 +19,13 @@ module.exports = function () {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* WARNING: Always leave this variable on line 30! If it is moved, you need to update ./scripts/bumpAndroidBuildNumber.sh
|
||||
*/
|
||||
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 |
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover">
|
||||
@@ -217,9 +217,9 @@
|
||||
{%- block body_all %}
|
||||
<div id="root"></div>
|
||||
<noscript>
|
||||
<h1>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>Learn more about Bluesky at <a href="https://blueskyweb.xyz">blueskyweb.xyz</a> and <a href="https://atproto.com">atproto.com</a>.
|
||||
<h1 lang="en">JavaScript Required</h1>
|
||||
<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 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 %}
|
||||
</noscript>
|
||||
{% endblock -%}
|
||||
|
||||
@@ -26,11 +26,22 @@
|
||||
- Start the dev servers
|
||||
- `git clone git@github.com:bluesky-social/atproto.git`
|
||||
- `cd atproto`
|
||||
- `brew install pnpm`
|
||||
- `brew install jq`
|
||||
- `pnpm i`
|
||||
- `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`
|
||||
- Run the dev app
|
||||
- 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`
|
||||
- Web: `yarn web`
|
||||
- If you are cloning or forking this repo as an open-source developer, please check the tips below as well
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
"production": {
|
||||
"ios": {
|
||||
"ascAppId": "6444370199"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -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:extract": "lingui extract",
|
||||
"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": {
|
||||
"@atproto/api": "^0.9.5",
|
||||
|
||||
Executable
+10
@@ -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"
|
||||
Executable
+10
@@ -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
@@ -32,6 +32,7 @@ import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||
import {Provider as PrefsStateProvider} from 'state/preferences'
|
||||
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
|
||||
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
|
||||
import I18nProvider from './locale/i18nProvider'
|
||||
import {
|
||||
Provider as SessionProvider,
|
||||
@@ -72,17 +73,19 @@ function InnerApp() {
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<LoggedOutViewProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<GestureHandlerRootView style={s.h100pct}>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
</GestureHandlerRootView>
|
||||
</RootSiblingParent>
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
<SelectedFeedProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<GestureHandlerRootView style={s.h100pct}>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
</GestureHandlerRootView>
|
||||
</RootSiblingParent>
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</React.Fragment>
|
||||
</Splash>
|
||||
|
||||
+14
-11
@@ -22,6 +22,7 @@ import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||
import {Provider as PrefsStateProvider} from 'state/preferences'
|
||||
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
|
||||
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
|
||||
import I18nProvider from './locale/i18nProvider'
|
||||
import {
|
||||
Provider as SessionProvider,
|
||||
@@ -52,17 +53,19 @@ function InnerApp() {
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<LoggedOutViewProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<SafeAreaProvider>
|
||||
<Shell />
|
||||
</SafeAreaProvider>
|
||||
</RootSiblingParent>
|
||||
<ToastContainer />
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
<SelectedFeedProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<SafeAreaProvider>
|
||||
<Shell />
|
||||
</SafeAreaProvider>
|
||||
</RootSiblingParent>
|
||||
<ToastContainer />
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</React.Fragment>
|
||||
</Alf>
|
||||
|
||||
@@ -146,7 +146,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
||||
getComponent={() => ProfileScreen}
|
||||
options={({route}) => ({
|
||||
title: bskyTitle(`@${route.params.name}`, unreadCountLabel),
|
||||
animation: 'none',
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
|
||||
+162
-40
@@ -73,19 +73,19 @@ export const darkPalette: Palette = {
|
||||
white: tokens.color.gray_0,
|
||||
black: tokens.color.trueBlack,
|
||||
|
||||
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,
|
||||
contrast_25: tokens.color.gray_1000,
|
||||
contrast_50: tokens.color.gray_975,
|
||||
contrast_100: tokens.color.gray_950,
|
||||
contrast_200: tokens.color.gray_900,
|
||||
contrast_300: tokens.color.gray_800,
|
||||
contrast_400: tokens.color.gray_700,
|
||||
contrast_500: tokens.color.gray_600,
|
||||
contrast_600: tokens.color.gray_500,
|
||||
contrast_700: tokens.color.gray_400,
|
||||
contrast_800: tokens.color.gray_300,
|
||||
contrast_900: tokens.color.gray_200,
|
||||
contrast_950: tokens.color.gray_100,
|
||||
contrast_975: tokens.color.gray_50,
|
||||
|
||||
primary_25: tokens.color.blue_25,
|
||||
primary_50: tokens.color.blue_50,
|
||||
@@ -133,6 +133,20 @@ export const darkPalette: Palette = {
|
||||
export const dimPalette: Palette = {
|
||||
...darkPalette,
|
||||
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
|
||||
|
||||
export const light = {
|
||||
@@ -142,17 +156,14 @@ export const light = {
|
||||
text: {
|
||||
color: lightPalette.black,
|
||||
},
|
||||
text_contrast_700: {
|
||||
text_contrast_low: {
|
||||
color: lightPalette.contrast_400,
|
||||
},
|
||||
text_contrast_medium: {
|
||||
color: lightPalette.contrast_700,
|
||||
},
|
||||
text_contrast_600: {
|
||||
color: lightPalette.contrast_600,
|
||||
},
|
||||
text_contrast_500: {
|
||||
color: lightPalette.contrast_500,
|
||||
},
|
||||
text_contrast_400: {
|
||||
color: lightPalette.contrast_400,
|
||||
text_contrast_high: {
|
||||
color: lightPalette.contrast_900,
|
||||
},
|
||||
text_inverted: {
|
||||
color: lightPalette.white,
|
||||
@@ -175,11 +186,38 @@ export const light = {
|
||||
bg_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,
|
||||
},
|
||||
border_contrast: {
|
||||
borderColor: lightPalette.contrast_400,
|
||||
border_contrast_medium: {
|
||||
borderColor: lightPalette.contrast_200,
|
||||
},
|
||||
border_contrast_high: {
|
||||
borderColor: lightPalette.contrast_300,
|
||||
},
|
||||
shadow_sm: {
|
||||
...atoms.shadow_sm,
|
||||
@@ -203,17 +241,14 @@ export const dark: Theme = {
|
||||
text: {
|
||||
color: darkPalette.white,
|
||||
},
|
||||
text_contrast_700: {
|
||||
text_contrast_low: {
|
||||
color: darkPalette.contrast_400,
|
||||
},
|
||||
text_contrast_medium: {
|
||||
color: darkPalette.contrast_700,
|
||||
},
|
||||
text_contrast_600: {
|
||||
color: darkPalette.contrast_600,
|
||||
},
|
||||
text_contrast_500: {
|
||||
color: darkPalette.contrast_500,
|
||||
},
|
||||
text_contrast_400: {
|
||||
color: darkPalette.contrast_400,
|
||||
text_contrast_high: {
|
||||
color: darkPalette.contrast_900,
|
||||
},
|
||||
text_inverted: {
|
||||
color: darkPalette.black,
|
||||
@@ -222,24 +257,51 @@ export const dark: Theme = {
|
||||
backgroundColor: darkPalette.black,
|
||||
},
|
||||
bg_contrast_25: {
|
||||
backgroundColor: darkPalette.contrast_50,
|
||||
backgroundColor: darkPalette.contrast_25,
|
||||
},
|
||||
bg_contrast_50: {
|
||||
backgroundColor: darkPalette.contrast_100,
|
||||
backgroundColor: darkPalette.contrast_50,
|
||||
},
|
||||
bg_contrast_100: {
|
||||
backgroundColor: darkPalette.contrast_200,
|
||||
backgroundColor: darkPalette.contrast_100,
|
||||
},
|
||||
bg_contrast_200: {
|
||||
backgroundColor: darkPalette.contrast_300,
|
||||
backgroundColor: darkPalette.contrast_200,
|
||||
},
|
||||
bg_contrast_300: {
|
||||
backgroundColor: darkPalette.contrast_300,
|
||||
},
|
||||
bg_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,
|
||||
},
|
||||
border_contrast: {
|
||||
border_contrast_medium: {
|
||||
borderColor: darkPalette.contrast_200,
|
||||
},
|
||||
border_contrast_high: {
|
||||
borderColor: darkPalette.contrast_300,
|
||||
},
|
||||
shadow_sm: {
|
||||
@@ -265,11 +327,71 @@ export const dim: Theme = {
|
||||
name: 'dim',
|
||||
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: {
|
||||
color: dimPalette.black,
|
||||
},
|
||||
bg: {
|
||||
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
@@ -13,13 +13,13 @@ export const color = {
|
||||
gray_300: `hsl(${BLUE_HUE}, 20%, 70%)`,
|
||||
gray_400: `hsl(${BLUE_HUE}, 20%, 60%)`,
|
||||
gray_500: `hsl(${BLUE_HUE}, 20%, 50%)`,
|
||||
gray_600: `hsl(${BLUE_HUE}, 20%, 42%)`,
|
||||
gray_700: `hsl(${BLUE_HUE}, 20%, 34%)`,
|
||||
gray_800: `hsl(${BLUE_HUE}, 20%, 26%)`,
|
||||
gray_900: `hsl(${BLUE_HUE}, 20%, 18%)`,
|
||||
gray_950: `hsl(${BLUE_HUE}, 20%, 10%)`,
|
||||
gray_975: `hsl(${BLUE_HUE}, 20%, 7%)`,
|
||||
gray_1000: `hsl(${BLUE_HUE}, 20%, 4%)`,
|
||||
gray_600: `hsl(${BLUE_HUE}, 24%, 42%)`,
|
||||
gray_700: `hsl(${BLUE_HUE}, 24%, 34%)`,
|
||||
gray_800: `hsl(${BLUE_HUE}, 28%, 26%)`,
|
||||
gray_900: `hsl(${BLUE_HUE}, 28%, 18%)`,
|
||||
gray_950: `hsl(${BLUE_HUE}, 28%, 10%)`,
|
||||
gray_975: `hsl(${BLUE_HUE}, 28%, 7%)`,
|
||||
gray_1000: `hsl(${BLUE_HUE}, 28%, 4%)`,
|
||||
|
||||
blue_25: `hsl(${BLUE_HUE}, 99%, 97%)`,
|
||||
blue_50: `hsl(${BLUE_HUE}, 99%, 95%)`,
|
||||
|
||||
@@ -52,6 +52,7 @@ export type ButtonProps = React.PropsWithChildren<
|
||||
Pick<PressableProps, 'disabled' | 'onPress' | 'testID'> &
|
||||
AccessibilityProps &
|
||||
VariantProps & {
|
||||
testID?: string
|
||||
label: string
|
||||
style?: StyleProp<ViewStyle>
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export function Outer({
|
||||
const sheet = React.useRef<BottomSheet>(null)
|
||||
const sheetOptions = nativeOptions?.sheet || {}
|
||||
const hasSnapPoints = !!sheetOptions.snapPoints
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
const open = React.useCallback<DialogControlProps['open']>(({index} = {}) => {
|
||||
sheet.current?.snapToIndex(index || 0)
|
||||
@@ -42,8 +43,7 @@ export function Outer({
|
||||
|
||||
const close = React.useCallback(() => {
|
||||
sheet.current?.close()
|
||||
onClose?.()
|
||||
}, [onClose])
|
||||
}, [])
|
||||
|
||||
useImperativeHandle(
|
||||
control.ref,
|
||||
@@ -54,6 +54,15 @@ export function Outer({
|
||||
[open, close],
|
||||
)
|
||||
|
||||
const onChange = React.useCallback(
|
||||
(index: number) => {
|
||||
if (index === -1) {
|
||||
onClose?.()
|
||||
}
|
||||
},
|
||||
[onClose],
|
||||
)
|
||||
|
||||
const context = React.useMemo(() => ({close}), [close])
|
||||
|
||||
return (
|
||||
@@ -64,6 +73,7 @@ export function Outer({
|
||||
keyboardBehavior="interactive"
|
||||
android_keyboardInputMode="adjustResize"
|
||||
keyboardBlurBehavior="restore"
|
||||
topInset={insets.top}
|
||||
{...sheetOptions}
|
||||
ref={sheet}
|
||||
index={defaultOpen ? 0 : -1}
|
||||
@@ -78,7 +88,7 @@ export function Outer({
|
||||
)}
|
||||
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
|
||||
handleStyle={{display: 'none'}}
|
||||
onClose={onClose}>
|
||||
onChange={onChange}>
|
||||
<Context.Provider value={context}>
|
||||
<View
|
||||
style={[
|
||||
@@ -122,6 +132,8 @@ export function ScrollableInner(props: DialogInnerProps) {
|
||||
const insets = useSafeAreaInsets()
|
||||
return (
|
||||
<BottomSheetScrollView
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag"
|
||||
style={[
|
||||
a.flex_1, // main diff is this
|
||||
a.p_xl,
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ViewStyleProp} from '#/alf'
|
||||
import {atoms as a, useTheme, ViewStyleProp, flatten} from '#/alf'
|
||||
|
||||
export function Divider({style}: ViewStyleProp) {
|
||||
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),
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useTheme, atoms as a} from '#/alf'
|
||||
import {H4, P} from '#/components/Typography'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {Button} from '#/components/Button'
|
||||
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
@@ -50,14 +50,11 @@ export function Outer({
|
||||
}
|
||||
|
||||
export function Title({children}: React.PropsWithChildren<{}>) {
|
||||
const t = useTheme()
|
||||
const {titleId} = React.useContext(Context)
|
||||
return (
|
||||
<H4
|
||||
nativeID={titleId}
|
||||
style={[a.font_bold, t.atoms.text_contrast_700, a.pb_sm]}>
|
||||
<Text nativeID={titleId} style={[a.text_2xl, a.font_bold, a.pb_sm]}>
|
||||
{children}
|
||||
</H4>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -65,9 +62,11 @@ export function Description({children}: React.PropsWithChildren<{}>) {
|
||||
const t = useTheme()
|
||||
const {descriptionId} = React.useContext(Context)
|
||||
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}
|
||||
</P>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+30
-126
@@ -15,7 +15,7 @@ export function leading<
|
||||
>(textSize: Size, leading: Leading) {
|
||||
const size = textSize?.fontSize || atoms.text_md.fontSize
|
||||
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 <= 2) {
|
||||
s.lineHeight = fontSize * s.lineHeight
|
||||
s.lineHeight = Math.round(fontSize * s.lineHeight)
|
||||
}
|
||||
} else {
|
||||
s.lineHeight = fontSize
|
||||
@@ -41,138 +41,42 @@ function normalizeTextStyles(styles: TextStyle[]) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Our main text component. Use this most of the time.
|
||||
*/
|
||||
export function Text({style, ...rest}: TextProps) {
|
||||
const t = useTheme()
|
||||
const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)])
|
||||
return <RNText style={s} {...rest} />
|
||||
}
|
||||
|
||||
export function H1({style, ...rest}: TextProps) {
|
||||
const t = useTheme()
|
||||
const attr =
|
||||
web({
|
||||
role: 'heading',
|
||||
'aria-level': 1,
|
||||
}) || {}
|
||||
return (
|
||||
<RNText
|
||||
{...attr}
|
||||
{...rest}
|
||||
style={normalizeTextStyles([
|
||||
atoms.text_5xl,
|
||||
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),
|
||||
])}
|
||||
/>
|
||||
)
|
||||
export function createHeadingElement({level}: {level: number}) {
|
||||
return function HeadingElement({style, ...rest}: TextProps) {
|
||||
const t = useTheme()
|
||||
const attr =
|
||||
web({
|
||||
role: 'heading',
|
||||
'aria-level': level,
|
||||
}) || {}
|
||||
return (
|
||||
<RNText
|
||||
{...attr}
|
||||
{...rest}
|
||||
style={normalizeTextStyles([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) {
|
||||
const t = useTheme()
|
||||
const attr =
|
||||
|
||||
@@ -241,10 +241,15 @@ export function createInput(Component: typeof TextInput) {
|
||||
|
||||
export const Input = createInput(TextInput)
|
||||
|
||||
export function Label({children}: React.PropsWithChildren<{}>) {
|
||||
export function Label({
|
||||
nativeID,
|
||||
children,
|
||||
}: React.PropsWithChildren<{nativeID?: string}>) {
|
||||
const t = useTheme()
|
||||
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}
|
||||
</Text>
|
||||
)
|
||||
@@ -318,7 +323,7 @@ export function Suffix({
|
||||
a.z_20,
|
||||
a.pr_sm,
|
||||
a.text_md,
|
||||
t.atoms.text_contrast_400,
|
||||
t.atoms.text_contrast_medium,
|
||||
{
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
|
||||
@@ -347,7 +347,7 @@ export function Checkbox() {
|
||||
a.align_center,
|
||||
a.border,
|
||||
a.rounded_xs,
|
||||
t.atoms.border_contrast,
|
||||
t.atoms.border_contrast_high,
|
||||
{
|
||||
height: 20,
|
||||
width: 20,
|
||||
@@ -393,7 +393,7 @@ export function Switch() {
|
||||
a.border,
|
||||
a.rounded_full,
|
||||
t.atoms.bg,
|
||||
t.atoms.border_contrast,
|
||||
t.atoms.border_contrast_high,
|
||||
{
|
||||
height: 20,
|
||||
width: 30,
|
||||
@@ -445,7 +445,7 @@ export function Radio() {
|
||||
a.align_center,
|
||||
a.border,
|
||||
a.rounded_full,
|
||||
t.atoms.border_contrast,
|
||||
t.atoms.border_contrast_high,
|
||||
{
|
||||
height: 20,
|
||||
width: 20,
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as Toggle from '#/components/forms/Toggle'
|
||||
|
||||
export type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> &
|
||||
AccessibilityProps &
|
||||
React.PropsWithChildren<{}>
|
||||
React.PropsWithChildren<{testID?: string}>
|
||||
|
||||
export type GroupProps = Omit<Toggle.GroupProps, 'style' | 'type'> & {
|
||||
multiple?: boolean
|
||||
@@ -25,7 +25,7 @@ export function Group({children, multiple, ...props}: GroupProps) {
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.overflow_hidden,
|
||||
t.atoms.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
@@ -103,7 +103,7 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
||||
}),
|
||||
a.px_sm,
|
||||
t.atoms.bg,
|
||||
t.atoms.border,
|
||||
t.atoms.border_contrast_low,
|
||||
baseStyles,
|
||||
activeStyles,
|
||||
(state.hovered || state.focused || state.pressed) && hoverStyles,
|
||||
@@ -113,7 +113,7 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
||||
style={[
|
||||
a.text_center,
|
||||
a.font_bold,
|
||||
t.atoms.text_contrast_500,
|
||||
t.atoms.text_contrast_medium,
|
||||
textStyles,
|
||||
]}>
|
||||
{children}
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
@@ -219,7 +219,7 @@ export const COUNTRY_CODES: CountryCodeMap[] = [
|
||||
{code2: 'SE', name: 'Sweden (+46)'},
|
||||
{code2: 'CH', name: 'Switzerland (+41)'},
|
||||
{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: 'TZ', name: 'Tanzania, United Republic of (+255)'},
|
||||
{code2: 'TH', name: 'Thailand (+66)'},
|
||||
|
||||
+1
-1
@@ -236,7 +236,7 @@ export function lh(
|
||||
height: number,
|
||||
): TextStyle {
|
||||
return {
|
||||
lineHeight: (theme.typography[type].fontSize || 16) * height,
|
||||
lineHeight: Math.round((theme.typography[type].fontSize || 16) * height),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:439
|
||||
msgid "Notifications"
|
||||
msgstr "Benachrichtigungen"
|
||||
msgstr "Mitteilungen"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:103
|
||||
msgid "Nudity"
|
||||
|
||||
@@ -9,8 +9,8 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-01-30 19:00+0900\n"
|
||||
"Last-Translator: dolciss\n"
|
||||
"Language-Team: Hima-Zinn, tkusano, dolciss, oboenikui, noritada\n"
|
||||
"Last-Translator: Hima-Zinn\n"
|
||||
"Language-Team: Hima-Zinn, tkusano, dolciss, oboenikui, noritada, middlingphys\n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:142
|
||||
@@ -242,7 +242,7 @@ msgstr "高度な設定"
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:217
|
||||
#: src/view/com/modals/ChangePassword.tsx:168
|
||||
msgid "Already have a code?"
|
||||
msgstr ""
|
||||
msgstr "コードをすでに持っていますか?"
|
||||
|
||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:98
|
||||
msgid "Already signed in as @{0}"
|
||||
@@ -280,7 +280,7 @@ msgstr "および"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:32
|
||||
msgid "Animals"
|
||||
msgstr ""
|
||||
msgstr "動物"
|
||||
|
||||
#: src/view/screens/LanguageSettings.tsx:95
|
||||
msgid "App Language"
|
||||
@@ -334,7 +334,7 @@ msgstr "この判断に異議を申し立てる"
|
||||
|
||||
#: src/view/screens/Settings.tsx:460
|
||||
msgid "Appearance"
|
||||
msgstr "外観"
|
||||
msgstr "背景"
|
||||
|
||||
#: src/view/screens/AppPasswords.tsx:224
|
||||
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
|
||||
msgid "Art"
|
||||
msgstr ""
|
||||
msgstr "アート"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:123
|
||||
msgid "Artistic or non-erotic nudity."
|
||||
@@ -476,7 +476,7 @@ msgstr "Blueskyはパブリックです。"
|
||||
|
||||
#: 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."
|
||||
msgstr "Blueskyはより健全なコミュニティを構築するために招待状を使用します。招待状をお持ちでない場合、Waitlistにお申し込みいただくと招待状をお送りします。"
|
||||
msgstr "Blueskyはより健全なコミュニティーを構築するために招待状を使用します。招待状をお持ちでない場合、Waitlistにお申し込みいただくと招待状をお送りします。"
|
||||
|
||||
#: 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."
|
||||
@@ -488,7 +488,7 @@ msgstr "Bluesky.Social"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:33
|
||||
msgid "Books"
|
||||
msgstr ""
|
||||
msgstr "書籍"
|
||||
|
||||
#: src/view/screens/Settings.tsx:841
|
||||
msgid "Build version {0} {1}"
|
||||
@@ -616,11 +616,11 @@ msgstr "メールアドレスを変更"
|
||||
|
||||
#: src/view/screens/Settings.tsx:726
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
msgstr "パスワードを変更"
|
||||
|
||||
#: src/view/screens/Settings.tsx:735
|
||||
msgid "Change Password"
|
||||
msgstr ""
|
||||
msgstr "パスワードを変更"
|
||||
|
||||
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73
|
||||
msgid "Change post language to {0}"
|
||||
@@ -628,7 +628,7 @@ msgstr "投稿の言語を{0}に変更します"
|
||||
|
||||
#: src/view/screens/Settings.tsx:727
|
||||
msgid "Change your Bluesky password"
|
||||
msgstr ""
|
||||
msgstr "Blueskyのパスワードを変更"
|
||||
|
||||
#: src/view/com/modals/ChangeEmail.tsx:109
|
||||
msgid "Change Your Email"
|
||||
@@ -682,7 +682,7 @@ msgstr "メインのフィードを選択"
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:163
|
||||
msgid "Choose your password"
|
||||
msgstr "パスワードを選択"
|
||||
msgstr "パスワードを入力"
|
||||
|
||||
#: src/view/screens/Settings.tsx:816
|
||||
#: src/view/screens/Settings.tsx:817
|
||||
@@ -713,12 +713,12 @@ msgstr "こちらをクリック"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:35
|
||||
msgid "Climate"
|
||||
msgstr ""
|
||||
msgstr "気象"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:265
|
||||
#: src/view/com/modals/ChangePassword.tsx:268
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "閉じる"
|
||||
|
||||
#: src/components/Dialog/index.web.tsx:78
|
||||
msgid "Close active dialog"
|
||||
@@ -766,16 +766,16 @@ msgstr "指定した通知のユーザーリストを折りたたむ"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:41
|
||||
msgid "Comedy"
|
||||
msgstr ""
|
||||
msgstr "コメディー"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:27
|
||||
msgid "Comics"
|
||||
msgstr ""
|
||||
msgstr "漫画"
|
||||
|
||||
#: src/Navigation.tsx:228
|
||||
#: src/view/screens/CommunityGuidelines.tsx:32
|
||||
msgid "Community Guidelines"
|
||||
msgstr "コミュニティガイドライン"
|
||||
msgstr "コミュニティーガイドライン"
|
||||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:148
|
||||
msgid "Complete onboarding and start using your account"
|
||||
@@ -898,7 +898,7 @@ msgstr "アカウントをフォローせずに次のステップへ進む"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:44
|
||||
msgid "Cooking"
|
||||
msgstr ""
|
||||
msgstr "料理"
|
||||
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:195
|
||||
#: src/view/com/modals/InviteCodes.tsx:182
|
||||
@@ -997,7 +997,7 @@ msgstr "サムネイル付きのカードを作成します。そのカードは
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:29
|
||||
msgid "Culture"
|
||||
msgstr ""
|
||||
msgstr "文化"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:389
|
||||
#: src/view/com/modals/ServerInput.tsx:102
|
||||
@@ -1006,7 +1006,7 @@ msgstr "カスタムドメイン"
|
||||
|
||||
#: 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."
|
||||
msgstr ""
|
||||
msgstr "コミュニティーによって作成されたカスタムフィードは、あなたに新しい体験をもたらし、あなたが好きなコンテンツを見つけるのに役立ちます。"
|
||||
|
||||
#: src/view/screens/PreferencesExternalEmbeds.tsx:55
|
||||
msgid "Customize media from external sites."
|
||||
@@ -1027,7 +1027,7 @@ msgstr "ダークモード"
|
||||
|
||||
#: src/view/screens/Settings.tsx:492
|
||||
msgid "Dark Theme"
|
||||
msgstr ""
|
||||
msgstr "ダークテーマ"
|
||||
|
||||
#: src/Navigation.tsx:204
|
||||
#~ msgid "Debug"
|
||||
@@ -1065,7 +1065,7 @@ msgstr "マイアカウントを削除"
|
||||
|
||||
#: src/view/screens/Settings.tsx:755
|
||||
msgid "Delete My Account…"
|
||||
msgstr ""
|
||||
msgstr "マイアカウントを削除…"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:228
|
||||
msgid "Delete post"
|
||||
@@ -1104,7 +1104,7 @@ msgstr "なにか言いたいことはあった?"
|
||||
|
||||
#: src/view/screens/Settings.tsx:498
|
||||
msgid "Dim"
|
||||
msgstr ""
|
||||
msgstr "グレー"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:144
|
||||
msgid "Discard"
|
||||
@@ -1210,7 +1210,7 @@ msgstr "例:返信として広告を繰り返し送ってくるユーザー。
|
||||
|
||||
#: src/view/com/modals/InviteCodes.tsx:96
|
||||
msgid "Each code works once. You'll receive more invite codes periodically."
|
||||
msgstr "それぞれのコードは一度ずつ動作します。定期的に招待コードをお送りします。"
|
||||
msgstr "それぞれのコードは一回限り有効です。定期的に追加の招待コードをお送りします。"
|
||||
|
||||
#: src/view/com/lists/ListMembers.tsx:149
|
||||
msgctxt "action"
|
||||
@@ -1266,7 +1266,7 @@ msgstr "あなたのプロフィールの説明を編集します"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:34
|
||||
msgid "Education"
|
||||
msgstr ""
|
||||
msgstr "教育"
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:143
|
||||
#: src/view/com/auth/create/Step2.tsx:194
|
||||
@@ -1342,7 +1342,7 @@ msgstr "確認コードを入力してください"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:151
|
||||
msgid "Enter the code you received to change your password."
|
||||
msgstr ""
|
||||
msgstr "パスワードを変更するために受け取ったコードを入力してください。"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:371
|
||||
msgid "Enter the domain you want to use"
|
||||
@@ -1535,7 +1535,7 @@ msgstr "ディスカッションスレッドを微調整します。"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:38
|
||||
msgid "Fitness"
|
||||
msgstr ""
|
||||
msgstr "フィットネス"
|
||||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:131
|
||||
msgid "Flexible"
|
||||
@@ -1623,7 +1623,7 @@ msgstr "あなたをフォロー"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:43
|
||||
msgid "Food"
|
||||
msgstr ""
|
||||
msgstr "食べ物"
|
||||
|
||||
#: src/view/com/modals/DeleteAccount.tsx:111
|
||||
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:191
|
||||
msgid "Get Started"
|
||||
msgstr "はじめに"
|
||||
msgstr "開始"
|
||||
|
||||
#: src/view/com/auth/LoggedOut.tsx:81
|
||||
#: src/view/com/auth/LoggedOut.tsx:82
|
||||
@@ -1825,11 +1825,11 @@ msgstr "ALTテキストが長い場合、ALTテキストの展開状態を切り
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:127
|
||||
msgid "If none are selected, suitable for all ages."
|
||||
msgstr "選択されていない場合は、すべての年齢に適しています。"
|
||||
msgstr "何も選択しない場合は、全年齢対象です。"
|
||||
|
||||
#: 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."
|
||||
msgstr ""
|
||||
msgstr "パスワードを変更する場合は、あなたのアカウントであることを確認するためのコードをお送りします。"
|
||||
|
||||
#: src/view/com/util/images/Gallery.tsx:38
|
||||
msgid "Image"
|
||||
@@ -1969,7 +1969,7 @@ msgstr "Waitlistに参加"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:24
|
||||
msgid "Journalism"
|
||||
msgstr ""
|
||||
msgstr "報道"
|
||||
|
||||
#: src/view/com/composer/select-language/SelectLangBtn.tsx:104
|
||||
msgid "Language selection"
|
||||
@@ -2272,7 +2272,7 @@ msgstr "モデレーションの設定"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:35
|
||||
msgid "Moderator has chosen to set a general warning on the content."
|
||||
msgstr "モデレーターはその投稿に一般的な警告の設定を選択しました。"
|
||||
msgstr "モデレーターによりコンテンツに一般的な警告が設定されました。"
|
||||
|
||||
#: src/view/shell/desktop/Feeds.tsx:53
|
||||
msgid "More feeds"
|
||||
@@ -2364,7 +2364,7 @@ msgstr "名前は必須です"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:25
|
||||
msgid "Nature"
|
||||
msgstr ""
|
||||
msgstr "自然"
|
||||
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:186
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:215
|
||||
@@ -2411,7 +2411,7 @@ msgstr "新しいパスワード"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:215
|
||||
msgid "New Password"
|
||||
msgstr ""
|
||||
msgstr "新しいパスワード"
|
||||
|
||||
#: src/view/com/feeds/FeedPage.tsx:201
|
||||
msgctxt "action"
|
||||
@@ -2446,7 +2446,7 @@ msgstr "新しい順に返信を表示"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:23
|
||||
msgid "News"
|
||||
msgstr ""
|
||||
msgstr "ニュース"
|
||||
|
||||
#: src/view/com/auth/create/CreateAccount.tsx:161
|
||||
#: 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:252
|
||||
msgid "Not right now"
|
||||
msgstr "今すぐにではない"
|
||||
msgstr "今はしない"
|
||||
|
||||
#: 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."
|
||||
@@ -2768,7 +2768,7 @@ msgstr "カメラへのアクセスが拒否されました。システムの設
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:31
|
||||
msgid "Pets"
|
||||
msgstr ""
|
||||
msgstr "ペット"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:183
|
||||
msgid "Phone number"
|
||||
@@ -2776,7 +2776,7 @@ msgstr "電話番号"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:121
|
||||
msgid "Pictures meant for adults."
|
||||
msgstr "成人向けの写真です。"
|
||||
msgstr "成人向けの画像です。"
|
||||
|
||||
#: src/view/screens/ProfileFeed.tsx:353
|
||||
#: src/view/screens/ProfileList.tsx:580
|
||||
@@ -2860,7 +2860,7 @@ msgstr "リンクカードがロードされるまでお待ちください"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:37
|
||||
msgid "Politics"
|
||||
msgstr ""
|
||||
msgstr "政治"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:111
|
||||
msgid "Porn"
|
||||
@@ -3187,7 +3187,7 @@ msgstr "コードをリクエスト"
|
||||
#: src/view/com/modals/ChangePassword.tsx:239
|
||||
#: src/view/com/modals/ChangePassword.tsx:241
|
||||
msgid "Request Code"
|
||||
msgstr ""
|
||||
msgstr "コードをリクエスト"
|
||||
|
||||
#: src/view/screens/Settings.tsx:450
|
||||
msgid "Require alt text before posting"
|
||||
@@ -3204,7 +3204,7 @@ msgstr "コードをリセット"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:190
|
||||
msgid "Reset Code"
|
||||
msgstr ""
|
||||
msgstr "コードをリセット"
|
||||
|
||||
#: src/view/screens/Settings.tsx:806
|
||||
msgid "Reset onboarding"
|
||||
@@ -3312,7 +3312,7 @@ msgstr "{handle}へのハンドルの変更を保存"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:36
|
||||
msgid "Science"
|
||||
msgstr ""
|
||||
msgstr "科学"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:854
|
||||
msgid "Scroll to top"
|
||||
@@ -3470,7 +3470,7 @@ msgstr "年齢を設定"
|
||||
|
||||
#: src/view/screens/Settings.tsx:482
|
||||
msgid "Set color theme to dark"
|
||||
msgstr "カラーテーマをダークに設定します"
|
||||
msgstr "カラーテーマを暗いものに設定します"
|
||||
|
||||
#: src/view/screens/Settings.tsx:475
|
||||
msgid "Set color theme to light"
|
||||
@@ -3478,15 +3478,15 @@ msgstr "カラーテーマをライトに設定します"
|
||||
|
||||
#: src/view/screens/Settings.tsx:469
|
||||
msgid "Set color theme to system setting"
|
||||
msgstr "システム設定のカラーテーマを使用するように設定します"
|
||||
msgstr "デバイスで設定したカラーテーマを使用するように設定します"
|
||||
|
||||
#: src/view/screens/Settings.tsx:508
|
||||
msgid "Set dark theme to the dark theme"
|
||||
msgstr ""
|
||||
msgstr "ダークテーマをダークに設定します"
|
||||
|
||||
#: src/view/screens/Settings.tsx:501
|
||||
msgid "Set dark theme to the dim theme"
|
||||
msgstr ""
|
||||
msgstr "ダークテーマを薄暗いものに設定します"
|
||||
|
||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:104
|
||||
msgid "Set new password"
|
||||
@@ -3717,7 +3717,7 @@ msgstr "サインアップ"
|
||||
|
||||
#: src/view/shell/NavSignupCard.tsx:42
|
||||
msgid "Sign up or sign in to join the conversation"
|
||||
msgstr "登録またはログインして会話に参加"
|
||||
msgstr "サインアップまたはサインインして会話に参加"
|
||||
|
||||
#: src/view/com/util/moderation/ScreenHider.tsx:76
|
||||
msgid "Sign-in Required"
|
||||
@@ -3751,7 +3751,7 @@ msgstr "SMS認証"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:40
|
||||
msgid "Software Dev"
|
||||
msgstr ""
|
||||
msgstr "ソフトウェア開発"
|
||||
|
||||
#: src/view/com/modals/ProfilePreview.tsx:62
|
||||
msgid "Something went wrong and we're not sure what."
|
||||
@@ -3775,7 +3775,7 @@ msgstr "次の方法で同じ投稿への返信を並び替えます。"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:30
|
||||
msgid "Sports"
|
||||
msgstr ""
|
||||
msgstr "スポーツ"
|
||||
|
||||
#: src/view/com/modals/crop-image/CropImage.web.tsx:122
|
||||
msgid "Square"
|
||||
@@ -3837,7 +3837,7 @@ msgstr "あなたへのおすすめ"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:95
|
||||
msgid "Suggestive"
|
||||
msgstr "提案"
|
||||
msgstr "きわどい"
|
||||
|
||||
#: src/Navigation.tsx:213
|
||||
#: src/view/screens/Support.tsx:30
|
||||
@@ -3881,7 +3881,7 @@ msgstr "タップして全体を表示"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:39
|
||||
msgid "Tech"
|
||||
msgstr ""
|
||||
msgstr "テクノロジー"
|
||||
|
||||
#: src/view/shell/desktop/RightNav.tsx:93
|
||||
msgid "Terms"
|
||||
@@ -3905,7 +3905,7 @@ msgstr "このアカウントは、ブロック解除後にあなたとやり取
|
||||
|
||||
#: src/view/screens/CommunityGuidelines.tsx:36
|
||||
msgid "The Community Guidelines have been moved to <0/>"
|
||||
msgstr "コミュニティガイドラインは<0/>に移動しました"
|
||||
msgstr "コミュニティーガイドラインは<0/>に移動しました"
|
||||
|
||||
#: src/view/screens/CopyrightPolicy.tsx:33
|
||||
msgid "The Copyright Policy has been moved to <0/>"
|
||||
@@ -4105,7 +4105,7 @@ msgstr "このユーザーは、あなたがブロックした<0/>リストに
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:74
|
||||
msgid "This user is included in the <0/> list which you have muted."
|
||||
msgstr ""
|
||||
msgstr "このユーザーは、あなたがミュートした<0/>リストに含まれています。"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:74
|
||||
#~ msgid "This user is included the <0/> list which you have muted."
|
||||
@@ -4113,7 +4113,7 @@ msgstr ""
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:137
|
||||
msgid "This warning is only available for posts with media attached."
|
||||
msgstr "この警告は、メディアが接続されている投稿にのみ使用できます。"
|
||||
msgstr "この警告は、メディアが添付されている投稿にのみ使用できます。"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:192
|
||||
msgid "This will hide this post from your feeds."
|
||||
@@ -4368,7 +4368,7 @@ msgstr "メールアドレスを確認"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:42
|
||||
msgid "Video Games"
|
||||
msgstr ""
|
||||
msgstr "ビデオゲーム"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:701
|
||||
msgid "View {0}'s avatar"
|
||||
@@ -4504,7 +4504,7 @@ msgstr "返信を書く"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:28
|
||||
msgid "Writers"
|
||||
msgstr ""
|
||||
msgstr "ライター"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:263
|
||||
msgid "XXXXXX"
|
||||
@@ -4568,7 +4568,7 @@ msgstr "保存されたフィードがありません。"
|
||||
|
||||
#: src/view/com/post-thread/PostThread.tsx:406
|
||||
msgid "You have blocked the author or you have been blocked by the author."
|
||||
msgstr "あなたが著者をブロックしているか、または著者によってあなたはブロックされています。"
|
||||
msgstr "あなたが投稿者をブロックしているか、または投稿者によってあなたはブロックされています。"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:56
|
||||
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:121
|
||||
msgid "You have entered an invalid code. It should look like XXXXX-XXXXX."
|
||||
msgstr ""
|
||||
msgstr "無効なコードが入力されました。それはXXXXX-XXXXXのようになっているはずです。"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:87
|
||||
msgid "You have muted this user."
|
||||
@@ -4676,7 +4676,7 @@ msgstr "メールアドレスが保存されました!すぐにご連絡いた
|
||||
|
||||
#: 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."
|
||||
msgstr "メールアドレスは更新されましたが、確認されていません。次のステップとして、新しいEメールを確認してください。"
|
||||
msgstr "メールアドレスは更新されましたが、確認されていません。次のステップとして、新しいメールアドレスを確認してください。"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:114
|
||||
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
|
||||
msgid "Your password has been changed successfully!"
|
||||
msgstr ""
|
||||
msgstr "パスワードの変更が完了しました!"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:267
|
||||
msgid "Your post has been published"
|
||||
|
||||
@@ -8,17 +8,11 @@ msgstr ""
|
||||
"Language: pt-BR\n"
|
||||
"Project-Id-Version: \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"
|
||||
"Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum\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
|
||||
msgid "(no email)"
|
||||
msgstr "(sem email)"
|
||||
@@ -27,15 +21,6 @@ msgstr "(sem email)"
|
||||
msgid "{0, plural, one {# invite code available} other {# invite codes available}}"
|
||||
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
|
||||
msgid "{following} following"
|
||||
msgstr "{following} seguindo"
|
||||
@@ -54,18 +39,10 @@ msgstr "{invitesAvailable} convite disponível"
|
||||
msgid "{invitesAvailable} invite codes available"
|
||||
msgstr "{invitesAvailable} convites disponíveis"
|
||||
|
||||
#: src/view/screens/Search/Search.tsx:87
|
||||
#~ msgid "{message}"
|
||||
#~ msgstr "{message}"
|
||||
|
||||
#: src/view/shell/Drawer.tsx:443
|
||||
msgid "{numUnreadNotifications} unread"
|
||||
msgstr "{numUnreadNotifications} não lidas"
|
||||
|
||||
#: src/Navigation.tsx:147
|
||||
#~ msgid "@{0}"
|
||||
#~ msgstr "@{0}"
|
||||
|
||||
#: src/view/com/threadgate/WhoCanReply.tsx:158
|
||||
msgid "<0/> members"
|
||||
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>"
|
||||
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
|
||||
msgid "<0>Welcome to</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/modals/ChangePassword.tsx:168
|
||||
msgid "Already have a code?"
|
||||
msgstr ""
|
||||
msgstr "Já tem um código?"
|
||||
|
||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:98
|
||||
msgid "Already signed in as @{0}"
|
||||
@@ -338,10 +303,6 @@ msgstr "Contestar aviso de conteúdo"
|
||||
msgid "Appeal Content Warning"
|
||||
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
|
||||
msgid "Appeal this decision"
|
||||
msgstr "Contestar esta decisão"
|
||||
@@ -354,10 +315,6 @@ msgstr "Contestar esta decisão."
|
||||
msgid "Appearance"
|
||||
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
|
||||
msgid "Are you sure you want to delete the app password \"{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."
|
||||
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/login/ChooseAccountForm.tsx:151
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||
@@ -589,10 +542,6 @@ msgstr "Cancelar"
|
||||
msgid "Cancel account deletion"
|
||||
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
|
||||
msgid "Cancel change handle"
|
||||
msgstr "Cancelar alteração de usuário"
|
||||
@@ -623,10 +572,6 @@ msgctxt "action"
|
||||
msgid "Change"
|
||||
msgstr "Alterar"
|
||||
|
||||
#: src/view/screens/Settings.tsx:306
|
||||
#~ msgid "Change"
|
||||
#~ msgstr "Alterar"
|
||||
|
||||
#: src/view/screens/Settings.tsx:690
|
||||
msgid "Change handle"
|
||||
msgstr "Alterar usuário"
|
||||
@@ -642,11 +587,11 @@ msgstr "Alterar meu email"
|
||||
|
||||
#: src/view/screens/Settings.tsx:726
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
msgstr "Alterar senha"
|
||||
|
||||
#: src/view/screens/Settings.tsx:735
|
||||
msgid "Change Password"
|
||||
msgstr ""
|
||||
msgstr "Alterar Senha"
|
||||
|
||||
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73
|
||||
msgid "Change post language to {0}"
|
||||
@@ -654,7 +599,7 @@ msgstr "Trocar idioma do post para {0}"
|
||||
|
||||
#: src/view/screens/Settings.tsx:727
|
||||
msgid "Change your Bluesky password"
|
||||
msgstr ""
|
||||
msgstr "Alterar sua senha do Bluesky"
|
||||
|
||||
#: src/view/com/modals/ChangeEmail.tsx:109
|
||||
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."
|
||||
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
|
||||
#~ msgid "Choose your algorithmic feeds"
|
||||
#~ 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:268
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Fechar"
|
||||
|
||||
#: src/components/Dialog/index.web.tsx:78
|
||||
msgid "Close active dialog"
|
||||
@@ -1036,7 +977,7 @@ msgstr "Domínio personalizado"
|
||||
|
||||
#: 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."
|
||||
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
|
||||
msgid "Customize media from external sites."
|
||||
@@ -1057,11 +998,7 @@ msgstr "Modo escuro"
|
||||
|
||||
#: src/view/screens/Settings.tsx:492
|
||||
msgid "Dark Theme"
|
||||
msgstr ""
|
||||
|
||||
#: src/Navigation.tsx:204
|
||||
#~ msgid "Debug"
|
||||
#~ msgstr "Depuração"
|
||||
msgstr "Modo Escuro"
|
||||
|
||||
#: src/view/screens/Debug.tsx:83
|
||||
msgid "Debug panel"
|
||||
@@ -1095,7 +1032,7 @@ msgstr "Excluir minha conta"
|
||||
|
||||
#: src/view/screens/Settings.tsx:755
|
||||
msgid "Delete My Account…"
|
||||
msgstr ""
|
||||
msgstr "Excluir minha conta…"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:228
|
||||
msgid "Delete post"
|
||||
@@ -1120,10 +1057,6 @@ msgstr "Post excluído."
|
||||
msgid "Description"
|
||||
msgstr "Descrição"
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:96
|
||||
#~ msgid "Dev Server"
|
||||
#~ msgstr "Servidor de Desenvolvimento"
|
||||
|
||||
#: src/view/screens/Settings.tsx:760
|
||||
msgid "Developer Tools"
|
||||
msgstr "Ferramentas de Desenvolvedor"
|
||||
@@ -1134,7 +1067,7 @@ msgstr "Você gostaria de dizer alguma coisa?"
|
||||
|
||||
#: src/view/screens/Settings.tsx:498
|
||||
msgid "Dim"
|
||||
msgstr ""
|
||||
msgstr "Menos escuro"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:144
|
||||
msgid "Discard"
|
||||
@@ -1366,13 +1299,9 @@ msgstr "Insira um nome para esta Senha de Aplicativo"
|
||||
msgid "Enter Confirmation Code"
|
||||
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
|
||||
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
|
||||
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"
|
||||
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
|
||||
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."
|
||||
@@ -1634,10 +1555,6 @@ msgstr "seguiu você"
|
||||
msgid "Followers"
|
||||
msgstr "Seguidores"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:624
|
||||
#~ msgid "following"
|
||||
#~ msgstr "seguindo"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:534
|
||||
#: src/view/screens/ProfileFollows.tsx:25
|
||||
msgid "Following"
|
||||
@@ -1743,10 +1660,6 @@ msgstr "Ajuda"
|
||||
msgid "Here are some accounts for you to follow"
|
||||
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
|
||||
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."
|
||||
@@ -1793,10 +1706,6 @@ msgstr "Ocultar lista de usuários"
|
||||
msgid "Hides posts from {0} in your 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
|
||||
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."
|
||||
@@ -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."
|
||||
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/view/shell/bottom-bar/BottomBar.tsx:137
|
||||
#: src/view/shell/desktop/LeftNav.tsx:306
|
||||
@@ -1840,11 +1745,6 @@ msgstr "Preferências da Página Inicial"
|
||||
msgid "Hosting provider"
|
||||
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
|
||||
msgid "How should we open this 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
|
||||
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
|
||||
msgid "Image"
|
||||
@@ -1886,11 +1786,6 @@ msgstr "Texto alternativo da imagem"
|
||||
msgid "Image options"
|
||||
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
|
||||
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"
|
||||
@@ -1903,14 +1798,6 @@ msgstr "Insira o código de confirmação para excluir sua conta"
|
||||
msgid "Input email for Bluesky account"
|
||||
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
|
||||
msgid "Input invite code to proceed"
|
||||
msgstr "Insira o convite para continuar"
|
||||
@@ -2124,14 +2011,6 @@ msgstr "Curtido por {likeCount} {0}"
|
||||
msgid "liked your custom 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
|
||||
msgid "liked your post"
|
||||
msgstr "curtiu seu post"
|
||||
@@ -2144,14 +2023,6 @@ msgstr "Curtidas"
|
||||
msgid "Likes on this 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
|
||||
msgid "List"
|
||||
msgstr "Lista"
|
||||
@@ -2231,10 +2102,6 @@ msgstr "Registros"
|
||||
msgid "Log out"
|
||||
msgstr "Sair"
|
||||
|
||||
#: src/view/screens/Moderation.tsx:134
|
||||
#~ msgid "Logged-out users"
|
||||
#~ msgstr "Visibilidade do seu perfil"
|
||||
|
||||
#: src/view/screens/Moderation.tsx:136
|
||||
msgid "Logged-out visibility"
|
||||
msgstr "Visibilidade do seu perfil"
|
||||
@@ -2243,10 +2110,6 @@ msgstr "Visibilidade do seu perfil"
|
||||
msgid "Login to account that is not listed"
|
||||
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
|
||||
msgid "Make sure this is where you intend to go!"
|
||||
msgstr "Certifique-se de onde está indo!"
|
||||
@@ -2268,10 +2131,6 @@ msgstr "Usuários mencionados"
|
||||
msgid "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
|
||||
msgid "Message from server: {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."
|
||||
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
|
||||
msgid "My Birthday"
|
||||
msgstr "Meu Aniversário"
|
||||
@@ -2466,7 +2321,7 @@ msgstr "Nova senha"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:215
|
||||
msgid "New Password"
|
||||
msgstr ""
|
||||
msgstr "Nova Senha"
|
||||
|
||||
#: src/view/com/feeds/FeedPage.tsx:201
|
||||
msgctxt "action"
|
||||
@@ -2487,10 +2342,6 @@ msgctxt "action"
|
||||
msgid "New Post"
|
||||
msgstr "Novo Post"
|
||||
|
||||
#: src/view/shell/desktop/LeftNav.tsx:258
|
||||
#~ msgid "New Post"
|
||||
#~ msgstr "Novo Post"
|
||||
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:247
|
||||
msgid "New User List"
|
||||
msgstr "Nova lista de usuários"
|
||||
@@ -2555,11 +2406,6 @@ msgstr "Nenhum resultado"
|
||||
msgid "No results found for \"{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/screens/Search/Search.tsx:280
|
||||
#: src/view/screens/Search/Search.tsx:308
|
||||
@@ -2574,10 +2420,6 @@ msgstr "Não, obrigado"
|
||||
msgid "Nobody"
|
||||
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
|
||||
msgid "Not Applicable."
|
||||
msgstr "Não Aplicável."
|
||||
@@ -2591,18 +2433,10 @@ msgstr "Não encontrado"
|
||||
msgid "Not right now"
|
||||
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
|
||||
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."
|
||||
|
||||
#: 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/view/screens/Notifications.tsx:120
|
||||
#: src/view/screens/Notifications.tsx:144
|
||||
@@ -2778,10 +2612,6 @@ msgstr "Opção {0} de {numItems}"
|
||||
msgid "Or combine these options:"
|
||||
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
|
||||
msgid "Other account"
|
||||
msgstr "Outra conta"
|
||||
@@ -2945,12 +2775,6 @@ msgctxt "description"
|
||||
msgid "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
|
||||
msgid "Post by {0}"
|
||||
msgstr "Post por {0}"
|
||||
@@ -3071,10 +2895,6 @@ msgctxt "action"
|
||||
msgid "Quote Post"
|
||||
msgstr "Citar Post"
|
||||
|
||||
#: src/view/com/modals/Repost.tsx:56
|
||||
#~ msgid "Quote Post"
|
||||
#~ msgstr "Citar Post"
|
||||
|
||||
#: src/view/screens/PreferencesThreads.tsx:86
|
||||
msgid "Random (aka \"Poster's Roulette\")"
|
||||
msgstr "Aleatório"
|
||||
@@ -3083,11 +2903,6 @@ msgstr "Aleatório"
|
||||
msgid "Ratios"
|
||||
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
|
||||
msgid "Recommended Feeds"
|
||||
msgstr "Feeds Recomendados"
|
||||
@@ -3220,10 +3035,6 @@ msgstr "Repostar"
|
||||
msgid "Repost or quote 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
|
||||
msgid "Reposted By"
|
||||
msgstr "Repostado Por"
|
||||
@@ -3232,10 +3043,6 @@ msgstr "Repostado Por"
|
||||
msgid "Reposted by {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
|
||||
msgid "Reposted by <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:241
|
||||
msgid "Request Code"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/screens/Moderation.tsx:188
|
||||
#~ msgid "Request to limit the visibility of my account"
|
||||
#~ msgstr "Exigir limitação de visibilidade da minha conta"
|
||||
msgstr "Solicitar Código"
|
||||
|
||||
#: src/view/screens/Settings.tsx:450
|
||||
msgid "Require alt text before posting"
|
||||
@@ -3281,7 +3084,7 @@ msgstr "Código de redefinição"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:190
|
||||
msgid "Reset Code"
|
||||
msgstr ""
|
||||
msgstr "Código de Redefinição"
|
||||
|
||||
#: src/view/screens/Settings.tsx:806
|
||||
msgid "Reset onboarding"
|
||||
@@ -3332,10 +3135,6 @@ msgstr "Tenta a última ação, que deu erro"
|
||||
msgid "Retry"
|
||||
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
|
||||
msgid "Retry."
|
||||
msgstr "Tentar novamente."
|
||||
@@ -3367,10 +3166,6 @@ msgstr "Salvar"
|
||||
msgid "Save alt text"
|
||||
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
|
||||
msgid "Save Changes"
|
||||
msgstr "Salvar Alterações"
|
||||
@@ -3425,10 +3220,6 @@ msgstr "Buscar"
|
||||
msgid "Search for \"{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:105
|
||||
#: src/view/com/modals/ListAddRemoveUsers.tsx:70
|
||||
@@ -3526,10 +3317,6 @@ msgctxt "action"
|
||||
msgid "Send Email"
|
||||
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:319
|
||||
msgid "Send feedback"
|
||||
@@ -3567,11 +3354,11 @@ msgstr "Definir o tema para acompanhar o sistema"
|
||||
|
||||
#: src/view/screens/Settings.tsx:508
|
||||
msgid "Set dark theme to the dark theme"
|
||||
msgstr ""
|
||||
msgstr "Definir o tema escuro para o padrão"
|
||||
|
||||
#: src/view/screens/Settings.tsx:501
|
||||
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
|
||||
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"
|
||||
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/login/LoginForm.tsx:148
|
||||
msgid "Sets server for the Bluesky client"
|
||||
@@ -3653,10 +3436,6 @@ msgstr "Compartilhar"
|
||||
msgid "Share feed"
|
||||
msgstr "Compartilhar feed"
|
||||
|
||||
#: src/view/screens/ProfileFeed.tsx:276
|
||||
#~ msgid "Share link"
|
||||
#~ msgstr "Compartilhar link"
|
||||
|
||||
#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:43
|
||||
#: src/view/com/modals/ContentFilteringSettings.tsx:261
|
||||
#: src/view/com/util/moderation/ContentHider.tsx:107
|
||||
@@ -3882,10 +3661,6 @@ msgstr "Página de status"
|
||||
msgid "Step {0} of {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
|
||||
msgid "Storage cleared, you need to restart the app now."
|
||||
msgstr "Armazenamento limpo, você precisa reiniciar o app agora."
|
||||
@@ -3912,10 +3687,6 @@ msgstr "Increver-se no feed {0}"
|
||||
msgid "Subscribe to this list"
|
||||
msgstr "Inscreva-se nesta lista"
|
||||
|
||||
#: src/view/com/lists/ListCard.tsx:101
|
||||
#~ msgid "Subscribed"
|
||||
#~ msgstr "Inscrito"
|
||||
|
||||
#: src/view/screens/Search/Search.tsx:373
|
||||
msgid "Suggested Follows"
|
||||
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."
|
||||
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
|
||||
msgid "The Terms of Service have been moved to"
|
||||
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:"
|
||||
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
|
||||
msgid "This {screenDescription} has been flagged:"
|
||||
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."
|
||||
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
|
||||
msgid "This link is taking you to the following website:"
|
||||
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
|
||||
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
|
||||
#~ msgid "This user is included the <0/> list which you have muted."
|
||||
@@ -4240,10 +3995,6 @@ msgctxt "action"
|
||||
msgid "Try again"
|
||||
msgstr "Tentar novamente"
|
||||
|
||||
#: src/view/com/util/error/ErrorScreen.tsx:73
|
||||
#~ msgid "Try again"
|
||||
#~ msgstr "Tente novamente"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:505
|
||||
msgid "Un-block list"
|
||||
msgstr "Desbloquear lista"
|
||||
@@ -4426,10 +4177,6 @@ msgstr "Usuários"
|
||||
msgid "users followed by <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
|
||||
msgid "Users in \"{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:"
|
||||
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
|
||||
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/>."
|
||||
@@ -4540,14 +4283,6 @@ msgstr "Usaremos isto para customizar a sua experiência."
|
||||
msgid "We're so excited to have you join us!"
|
||||
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
|
||||
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}."
|
||||
@@ -4638,10 +4373,6 @@ msgstr "Você também pode descobrir novos feeds para seguir."
|
||||
#~ msgid "You can also try our \"Discover\" algorithm:"
|
||||
#~ 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
|
||||
msgid "You can change these settings later."
|
||||
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:121
|
||||
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
|
||||
msgid "You have muted this user."
|
||||
@@ -4795,10 +4526,6 @@ msgstr "Seu identificador completo será"
|
||||
msgid "Your full handle will be <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/shell/desktop/RightNav.tsx:137
|
||||
#: 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
|
||||
msgid "Your password has been changed successfully!"
|
||||
msgstr ""
|
||||
msgstr "Sua senha foi alterada com sucesso!"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:267
|
||||
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"
|
||||
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
|
||||
msgid "Your reply has been published"
|
||||
msgstr "Sua resposta foi publicada"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
msgid ""
|
||||
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"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@@ -9,8 +9,8 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Frudrax Cheng <i@cynosura.one>\n"
|
||||
"Language-Team: Frudrax Cheng, Simon Chan, U2FsdGVkX1\n"
|
||||
"Last-Translator: Mikan Harada <i@3kn.jp>\n"
|
||||
"Language-Team: Frudrax Cheng, Simon Chan, U2FsdGVkX1, Mikan Harada\n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:142
|
||||
@@ -19,7 +19,7 @@ msgstr "(没有邮件)"
|
||||
|
||||
#: src/view/shell/desktop/RightNav.tsx:168
|
||||
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
|
||||
msgid "{following} following"
|
||||
@@ -32,12 +32,12 @@ msgstr "{invitesAvailable, plural, one {邀请码: # 可用} other {邀请码: #
|
||||
#: src/view/screens/Settings.tsx:435
|
||||
#: src/view/shell/Drawer.tsx:664
|
||||
msgid "{invitesAvailable} invite code available"
|
||||
msgstr "{invitesAvailable} 邀请码可用"
|
||||
msgstr "{invitesAvailable} 条邀请码可用"
|
||||
|
||||
#: src/view/screens/Settings.tsx:437
|
||||
#: src/view/shell/Drawer.tsx:666
|
||||
msgid "{invitesAvailable} invite codes available"
|
||||
msgstr "{invitesAvailable} 邀请码可用"
|
||||
msgstr "{invitesAvailable} 条邀请码可用"
|
||||
|
||||
#: src/view/shell/Drawer.tsx:443
|
||||
msgid "{numUnreadNotifications} unread"
|
||||
@@ -65,7 +65,7 @@ msgstr "<0>欢迎来到</0><1>Bluesky</1>"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:597
|
||||
msgid "⚠Invalid Handle"
|
||||
msgstr "⚠无效的昵称"
|
||||
msgstr "⚠无效的用户识别符"
|
||||
|
||||
#: src/view/com/util/moderation/LabelInfo.tsx:45
|
||||
msgid "A content warning has been applied to this {0}."
|
||||
@@ -101,15 +101,15 @@ msgstr "已屏蔽账户"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:260
|
||||
msgid "Account muted"
|
||||
msgstr "已静音账户"
|
||||
msgstr "已隐藏账户"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:86
|
||||
msgid "Account Muted"
|
||||
msgstr "已静音账户"
|
||||
msgstr "已隐藏账户"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:72
|
||||
msgid "Account Muted by List"
|
||||
msgstr "账户已被列表静音"
|
||||
msgstr "账户已被列表隐藏"
|
||||
|
||||
#: src/view/com/util/AccountDropdownBtn.tsx:41
|
||||
msgid "Account options"
|
||||
@@ -125,7 +125,7 @@ msgstr "已取消屏蔽账户"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:273
|
||||
msgid "Account unmuted"
|
||||
msgstr "已取消静音账户"
|
||||
msgstr "已取消隐藏账户"
|
||||
|
||||
#: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:150
|
||||
#: src/view/com/modals/ListAddRemoveUsers.tsx:264
|
||||
@@ -221,7 +221,7 @@ msgstr "详细设置"
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:217
|
||||
#: src/view/com/modals/ChangePassword.tsx:168
|
||||
msgid "Already have a code?"
|
||||
msgstr ""
|
||||
msgstr "已经有确认码了?"
|
||||
|
||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:98
|
||||
msgid "Already signed in as @{0}"
|
||||
@@ -540,7 +540,7 @@ msgstr "撤销账户删除申请"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:149
|
||||
msgid "Cancel change handle"
|
||||
msgstr "撤销修改昵称"
|
||||
msgstr "撤销修改用户识别符"
|
||||
|
||||
#: src/view/com/modals/crop-image/CropImage.web.tsx:134
|
||||
msgid "Cancel image crop"
|
||||
@@ -570,12 +570,12 @@ msgstr "更改"
|
||||
|
||||
#: src/view/screens/Settings.tsx:690
|
||||
msgid "Change handle"
|
||||
msgstr "更改昵称"
|
||||
msgstr "更改用户识别符"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:161
|
||||
#: src/view/screens/Settings.tsx:699
|
||||
msgid "Change Handle"
|
||||
msgstr "更改昵称"
|
||||
msgstr "更改用户识别符"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:147
|
||||
msgid "Change my email"
|
||||
@@ -583,11 +583,11 @@ msgstr "更改我的邮箱地址"
|
||||
|
||||
#: src/view/screens/Settings.tsx:726
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
msgstr "更改密码"
|
||||
|
||||
#: src/view/screens/Settings.tsx:735
|
||||
msgid "Change Password"
|
||||
msgstr ""
|
||||
msgstr "更改密码"
|
||||
|
||||
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73
|
||||
msgid "Change post language to {0}"
|
||||
@@ -595,7 +595,7 @@ msgstr "更改帖子的发布语言至 {0}"
|
||||
|
||||
#: src/view/screens/Settings.tsx:727
|
||||
msgid "Change your Bluesky password"
|
||||
msgstr ""
|
||||
msgstr "更改你的 Bluesky 密码"
|
||||
|
||||
#: src/view/com/modals/ChangeEmail.tsx:109
|
||||
msgid "Change Your Email"
|
||||
@@ -681,7 +681,7 @@ msgstr "气象"
|
||||
#: src/view/com/modals/ChangePassword.tsx:265
|
||||
#: src/view/com/modals/ChangePassword.tsx:268
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "关闭"
|
||||
|
||||
#: src/components/Dialog/index.web.tsx:78
|
||||
msgid "Close active dialog"
|
||||
@@ -969,7 +969,7 @@ msgstr "自定义域名"
|
||||
|
||||
#: 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."
|
||||
msgstr ""
|
||||
msgstr "由社区构建的自定义信息流能为你带来新的体验,并帮助你找到你喜欢的内容。"
|
||||
|
||||
#: src/view/screens/PreferencesExternalEmbeds.tsx:55
|
||||
msgid "Customize media from external sites."
|
||||
@@ -982,15 +982,15 @@ msgstr "自定义外部站点的媒体。"
|
||||
#: src/view/screens/Settings.tsx:479
|
||||
#: src/view/screens/Settings.tsx:505
|
||||
msgid "Dark"
|
||||
msgstr "暗色"
|
||||
msgstr "深黑"
|
||||
|
||||
#: src/view/screens/Debug.tsx:63
|
||||
msgid "Dark mode"
|
||||
msgstr "暗色模式"
|
||||
msgstr "深色模式"
|
||||
|
||||
#: src/view/screens/Settings.tsx:492
|
||||
msgid "Dark Theme"
|
||||
msgstr ""
|
||||
msgstr "深色模式"
|
||||
|
||||
#: src/view/screens/Debug.tsx:83
|
||||
msgid "Debug panel"
|
||||
@@ -1024,7 +1024,7 @@ msgstr "删除我的账户"
|
||||
|
||||
#: src/view/screens/Settings.tsx:755
|
||||
msgid "Delete My Account…"
|
||||
msgstr ""
|
||||
msgstr "删除我的账户…"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:228
|
||||
msgid "Delete post"
|
||||
@@ -1059,7 +1059,7 @@ msgstr "有什么想说的吗?"
|
||||
|
||||
#: src/view/screens/Settings.tsx:498
|
||||
msgid "Dim"
|
||||
msgstr ""
|
||||
msgstr "暗淡"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:144
|
||||
msgid "Discard"
|
||||
@@ -1137,7 +1137,7 @@ msgstr "拖放即可添加图片"
|
||||
|
||||
#: 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."
|
||||
msgstr "受 Apple 政策限制,成人内容只能在完成注册后在网页端启用显示"
|
||||
msgstr "受 Apple 政策限制,成人内容只能在完成注册后在网页端启用显示。"
|
||||
|
||||
#: src/view/com/modals/EditProfile.tsx:185
|
||||
msgid "e.g. Alice Roberts"
|
||||
@@ -1183,7 +1183,7 @@ msgstr "编辑列表详情"
|
||||
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:250
|
||||
msgid "Edit Moderation List"
|
||||
msgstr "编辑管理员列表"
|
||||
msgstr "编辑限制列表"
|
||||
|
||||
#: src/Navigation.tsx:243
|
||||
#: src/view/screens/Feeds.tsx:403
|
||||
@@ -1293,7 +1293,7 @@ msgstr "输入验证码"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:151
|
||||
msgid "Enter the code you received to change your password."
|
||||
msgstr ""
|
||||
msgstr "输入你收到的确认码以更改密码。"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:371
|
||||
msgid "Enter the domain you want to use"
|
||||
@@ -1342,7 +1342,7 @@ msgstr "所有人"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:150
|
||||
msgid "Exits handle change process"
|
||||
msgstr "退出修改昵称流程"
|
||||
msgstr "退出修改用户识别符流程"
|
||||
|
||||
#: src/view/com/lightbox/Lightbox.web.tsx:120
|
||||
msgid "Exits image view"
|
||||
@@ -1388,7 +1388,7 @@ msgstr "外部媒体设置"
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:115
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:119
|
||||
msgid "Failed to create app password."
|
||||
msgstr "无法创建 App 专用密码。"
|
||||
msgstr "创建 App 专用密码失败。"
|
||||
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:206
|
||||
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
|
||||
msgid "Handle"
|
||||
msgstr "昵称"
|
||||
msgstr "用户识别符"
|
||||
|
||||
#: src/view/com/auth/create/CreateAccount.tsx:197
|
||||
msgid "Having trouble?"
|
||||
@@ -1739,7 +1739,7 @@ msgstr "我们该如何打开此链接?"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:214
|
||||
msgid "I have a code"
|
||||
msgstr "我有邀请码"
|
||||
msgstr "我有验证码"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:216
|
||||
msgid "I have a confirmation code"
|
||||
@@ -1759,7 +1759,7 @@ msgstr "若不勾选,则默认为全年龄向。"
|
||||
|
||||
#: 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."
|
||||
msgstr ""
|
||||
msgstr "如果你想要更改密码,我们将向你发送一个确认码以验证这是你的账户。"
|
||||
|
||||
#: src/view/com/util/images/Gallery.tsx:38
|
||||
msgid "Image"
|
||||
@@ -1828,7 +1828,7 @@ msgstr "输入你的密码"
|
||||
|
||||
#: src/view/com/auth/create/Step3.tsx:42
|
||||
msgid "Input your user handle"
|
||||
msgstr "输入你的用户昵称"
|
||||
msgstr "输入你的用户识别符"
|
||||
|
||||
#: src/view/com/post-thread/PostThreadItem.tsx:231
|
||||
msgid "Invalid or unsupported post record"
|
||||
@@ -1845,7 +1845,7 @@ msgstr "邀请"
|
||||
#: src/view/com/modals/InviteCodes.tsx:93
|
||||
#: src/view/screens/Settings.tsx:399
|
||||
msgid "Invite a Friend"
|
||||
msgstr "邀请一位朋友"
|
||||
msgstr "邀请朋友"
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:92
|
||||
#: src/view/com/auth/create/Step1.tsx:101
|
||||
@@ -2033,7 +2033,7 @@ msgstr "列表已删除"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:282
|
||||
msgid "List muted"
|
||||
msgstr "列表已静音"
|
||||
msgstr "列表已隐藏"
|
||||
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:275
|
||||
msgid "List Name"
|
||||
@@ -2045,7 +2045,7 @@ msgstr "取消屏蔽列表"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:301
|
||||
msgid "List unmuted"
|
||||
msgstr "取消静音列表"
|
||||
msgstr "取消隐藏列表"
|
||||
|
||||
#: src/Navigation.tsx:110
|
||||
#: src/view/screens/Profile.tsx:176
|
||||
@@ -2092,7 +2092,7 @@ msgstr "登出"
|
||||
|
||||
#: src/view/screens/Moderation.tsx:136
|
||||
msgid "Logged-out visibility"
|
||||
msgstr "登出可见性"
|
||||
msgstr "未登录用户可见性"
|
||||
|
||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:133
|
||||
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:515
|
||||
msgid "Moderation"
|
||||
msgstr "管理员"
|
||||
msgstr "限制"
|
||||
|
||||
#: src/view/com/lists/ListCard.tsx:92
|
||||
#: src/view/com/modals/UserAddRemoveLists.tsx:206
|
||||
msgid "Moderation list by {0}"
|
||||
msgstr "管理员列表由 {0} 创建"
|
||||
msgstr "限制列表由 {0} 创建"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:774
|
||||
msgid "Moderation list by <0/>"
|
||||
msgstr "管理员列表由 </0> 创建"
|
||||
msgstr "限制列表由 </0> 创建"
|
||||
|
||||
#: src/view/com/lists/ListCard.tsx:90
|
||||
#: src/view/com/modals/UserAddRemoveLists.tsx:204
|
||||
#: src/view/screens/ProfileList.tsx:772
|
||||
msgid "Moderation list by you"
|
||||
msgstr "管理员列表由你创建"
|
||||
msgstr "限制列表由你创建"
|
||||
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:197
|
||||
msgid "Moderation list created"
|
||||
msgstr "管理员列表已创建"
|
||||
msgstr "限制列表已创建"
|
||||
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:183
|
||||
msgid "Moderation list updated"
|
||||
msgstr "管理员列表已更新"
|
||||
msgstr "限制列表已更新"
|
||||
|
||||
#: src/view/screens/Moderation.tsx:95
|
||||
msgid "Moderation lists"
|
||||
msgstr "管理员列表"
|
||||
msgstr "限制列表"
|
||||
|
||||
#: src/Navigation.tsx:120
|
||||
#: src/view/screens/ModerationModlists.tsx:58
|
||||
msgid "Moderation Lists"
|
||||
msgstr "管理员列表"
|
||||
msgstr "限制列表"
|
||||
|
||||
#: src/view/screens/Settings.tsx:613
|
||||
msgid "Moderation settings"
|
||||
msgstr "管理员设置"
|
||||
msgstr "限制设置"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:35
|
||||
msgid "Moderator has chosen to set a general warning on the content."
|
||||
msgstr "管理员选择对内容设置一般警告。"
|
||||
msgstr "限制选择对内容设置一般警告。"
|
||||
|
||||
#: src/view/shell/desktop/Feeds.tsx:53
|
||||
msgid "More feeds"
|
||||
@@ -2192,48 +2192,48 @@ msgstr "最多点赞优先"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:374
|
||||
msgid "Mute Account"
|
||||
msgstr "静音账户"
|
||||
msgstr "隐藏账户"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:543
|
||||
msgid "Mute accounts"
|
||||
msgstr "静音账户"
|
||||
msgstr "隐藏账户"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:490
|
||||
msgid "Mute list"
|
||||
msgstr "静音列表"
|
||||
msgstr "隐藏列表"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:274
|
||||
msgid "Mute these accounts?"
|
||||
msgstr "静音这些账户?"
|
||||
msgstr "隐藏这些账户?"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:278
|
||||
msgid "Mute this List"
|
||||
msgstr "静音这个列表"
|
||||
msgstr "隐藏这个列表"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:171
|
||||
msgid "Mute thread"
|
||||
msgstr "静音讨论串"
|
||||
msgstr "隐藏讨论串"
|
||||
|
||||
#: src/view/com/lists/ListCard.tsx:101
|
||||
msgid "Muted"
|
||||
msgstr "已经印"
|
||||
msgstr "已隐藏"
|
||||
|
||||
#: src/view/screens/Moderation.tsx:109
|
||||
msgid "Muted accounts"
|
||||
msgstr "已静音账户"
|
||||
msgstr "已隐藏账户"
|
||||
|
||||
#: src/Navigation.tsx:125
|
||||
#: src/view/screens/ModerationMutedAccounts.tsx:107
|
||||
msgid "Muted Accounts"
|
||||
msgstr "已静音账户"
|
||||
msgstr "已隐藏账户"
|
||||
|
||||
#: src/view/screens/ModerationMutedAccounts.tsx:115
|
||||
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
|
||||
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
|
||||
msgid "My Birthday"
|
||||
@@ -2293,15 +2293,15 @@ msgstr "永远不会失去对你的关注者或数据的访问。"
|
||||
#: src/view/screens/Lists.tsx:76
|
||||
msgctxt "action"
|
||||
msgid "New"
|
||||
msgstr "新"
|
||||
msgstr "新建"
|
||||
|
||||
#: src/view/screens/ModerationModlists.tsx:78
|
||||
msgid "New"
|
||||
msgstr "新"
|
||||
msgstr "新建"
|
||||
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:252
|
||||
msgid "New Moderation List"
|
||||
msgstr "新的管理员列表"
|
||||
msgstr "新的限制列表"
|
||||
|
||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:150
|
||||
msgid "New password"
|
||||
@@ -2309,7 +2309,7 @@ msgstr "新密码"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:215
|
||||
msgid "New Password"
|
||||
msgstr ""
|
||||
msgstr "新密码"
|
||||
|
||||
#: src/view/com/feeds/FeedPage.tsx:201
|
||||
msgctxt "action"
|
||||
@@ -2370,7 +2370,7 @@ msgstr "下一张图片"
|
||||
#: src/view/screens/PreferencesThreads.tsx:106
|
||||
#: src/view/screens/PreferencesThreads.tsx:129
|
||||
msgid "No"
|
||||
msgstr "没有"
|
||||
msgstr "停用"
|
||||
|
||||
#: src/view/screens/ProfileFeed.tsx:584
|
||||
#: 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:308
|
||||
msgid "No results found for {query}"
|
||||
msgstr "未找到{query}的结果"
|
||||
msgstr "未找到 {query} 的结果"
|
||||
|
||||
#: src/view/com/modals/EmbedConsent.tsx:129
|
||||
msgid "No thanks"
|
||||
@@ -2480,7 +2480,7 @@ msgstr "打开"
|
||||
#: src/view/com/composer/Composer.tsx:470
|
||||
#: src/view/com/composer/Composer.tsx:471
|
||||
msgid "Open emoji picker"
|
||||
msgstr "打开emoji选择器"
|
||||
msgstr "打开 emoji 选择器"
|
||||
|
||||
#: src/view/screens/Settings.tsx:706
|
||||
msgid "Open links with in-app browser"
|
||||
@@ -2558,7 +2558,7 @@ msgstr "打开使用自定义域名的模式"
|
||||
|
||||
#: src/view/screens/Settings.tsx:614
|
||||
msgid "Opens moderation settings"
|
||||
msgstr "打开管理员设置"
|
||||
msgstr "打开限制设置"
|
||||
|
||||
#: src/view/com/auth/login/LoginForm.tsx:236
|
||||
msgid "Opens password reset form"
|
||||
@@ -2688,11 +2688,11 @@ msgstr "播放 GIF"
|
||||
|
||||
#: src/view/com/auth/create/state.ts:177
|
||||
msgid "Please choose your handle."
|
||||
msgstr "请选择你的昵称。"
|
||||
msgstr "请设置你的用户识别符。"
|
||||
|
||||
#: src/view/com/auth/create/state.ts:160
|
||||
msgid "Please choose your password."
|
||||
msgstr "请选择你的密码。"
|
||||
msgstr "请设置你的密码。"
|
||||
|
||||
#: 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."
|
||||
@@ -2850,7 +2850,7 @@ msgstr "公开内容"
|
||||
|
||||
#: src/view/screens/ModerationModlists.tsx:61
|
||||
msgid "Public, shareable lists of users to mute or block in bulk."
|
||||
msgstr "公开且可共享的批量静音或屏蔽列表。"
|
||||
msgstr "公开且可共享的批量隐藏或屏蔽列表。"
|
||||
|
||||
#: src/view/screens/Lists.tsx:61
|
||||
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:241
|
||||
msgid "Request Code"
|
||||
msgstr ""
|
||||
msgstr "确认码"
|
||||
|
||||
#: src/view/screens/Settings.tsx:450
|
||||
msgid "Require alt text before posting"
|
||||
@@ -3067,7 +3067,7 @@ msgstr "重置码"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:190
|
||||
msgid "Reset Code"
|
||||
msgstr ""
|
||||
msgstr "重置代码"
|
||||
|
||||
#: src/view/screens/Settings.tsx:806
|
||||
msgid "Reset onboarding"
|
||||
@@ -3155,7 +3155,7 @@ msgstr "保存更改"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:170
|
||||
msgid "Save handle change"
|
||||
msgstr "保存新的昵称"
|
||||
msgstr "保存新的用户识别符"
|
||||
|
||||
#: src/view/com/modals/crop-image/CropImage.web.tsx:144
|
||||
msgid "Save image crop"
|
||||
@@ -3171,7 +3171,7 @@ msgstr "保存个人资料中所做的变更"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:171
|
||||
msgid "Saves handle change to {handle}"
|
||||
msgstr "保存昵称更改至 {handle}"
|
||||
msgstr "保存用户识别符更改至 {handle}"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:36
|
||||
msgid "Science"
|
||||
@@ -3321,7 +3321,7 @@ msgstr "设置年龄"
|
||||
|
||||
#: src/view/screens/Settings.tsx:482
|
||||
msgid "Set color theme to dark"
|
||||
msgstr "设置主题为暗色模式"
|
||||
msgstr "设置主题为深色模式"
|
||||
|
||||
#: src/view/screens/Settings.tsx:475
|
||||
msgid "Set color theme to light"
|
||||
@@ -3333,11 +3333,11 @@ msgstr "设置主题跟随系统设置"
|
||||
|
||||
#: src/view/screens/Settings.tsx:508
|
||||
msgid "Set dark theme to the dark theme"
|
||||
msgstr ""
|
||||
msgstr "设置深色模式至深黑"
|
||||
|
||||
#: src/view/screens/Settings.tsx:501
|
||||
msgid "Set dark theme to the dim theme"
|
||||
msgstr ""
|
||||
msgstr "设置深色模式至暗淡"
|
||||
|
||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:104
|
||||
msgid "Set new password"
|
||||
@@ -3349,23 +3349,23 @@ msgstr "设置密码"
|
||||
|
||||
#: 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."
|
||||
msgstr "将此设置项设为\"否\"以隐藏来自订阅信息流的所有引用帖子,转发仍将可见。"
|
||||
msgstr "停用此设置项以隐藏来自订阅信息流的所有引用帖子,转发仍将可见。"
|
||||
|
||||
#: src/view/screens/PreferencesHomeFeed.tsx:122
|
||||
msgid "Set this setting to \"No\" to hide all replies from your feed."
|
||||
msgstr "将此设置项设为\"否\"以隐藏来自订阅信息流的所有回复。"
|
||||
msgstr "停用此设置项以隐藏来自订阅信息流的所有回复。"
|
||||
|
||||
#: src/view/screens/PreferencesHomeFeed.tsx:191
|
||||
msgid "Set this setting to \"No\" to hide all reposts from your feed."
|
||||
msgstr "将此设置项设为\"否\"以隐藏来自订阅信息流的所有转发。"
|
||||
msgstr "停用此设置项以隐藏来自订阅信息流的所有转发。"
|
||||
|
||||
#: src/view/screens/PreferencesThreads.tsx:122
|
||||
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
|
||||
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
|
||||
msgid "Set up your account"
|
||||
@@ -3846,7 +3846,7 @@ msgstr "出现问题了,请检查你的互联网连接并重试。"
|
||||
|
||||
#: 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!"
|
||||
msgstr "\"应用发生意外错误,请联系我们进行错误反馈!"
|
||||
msgstr "应用发生意外错误,请联系我们进行错误反馈!"
|
||||
|
||||
#: 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."
|
||||
@@ -3862,7 +3862,7 @@ msgstr "这里是一些受欢迎的账号,你可能会喜欢:"
|
||||
|
||||
#: src/view/com/util/moderation/ScreenHider.tsx:88
|
||||
msgid "This {screenDescription} has been flagged:"
|
||||
msgstr "{screenDescription}\" 已被标记:"
|
||||
msgstr "这个 {screenDescription} 已被标记:"
|
||||
|
||||
#: src/view/com/util/moderation/ScreenHider.tsx:83
|
||||
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
|
||||
msgid "This user is included in the <0/> list which you have muted."
|
||||
msgstr ""
|
||||
msgstr "此用户包含在你已隐藏的 <0/> 列表中。"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:74
|
||||
#~ msgid "This user is included the <0/> list which you have muted."
|
||||
#~ msgstr "此用户包含在你已静音的 <0/> 列表中。"
|
||||
#~ msgstr "此用户包含在你已隐藏的 <0/> 列表中。"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:137
|
||||
msgid "This warning is only available for posts with media attached."
|
||||
@@ -3980,7 +3980,7 @@ msgstr "取消屏蔽列表"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:490
|
||||
msgid "Un-mute list"
|
||||
msgstr "取消静音列表"
|
||||
msgstr "取消隐藏列表"
|
||||
|
||||
#: src/view/com/auth/create/CreateAccount.tsx:66
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:87
|
||||
@@ -4031,15 +4031,15 @@ msgstr "取消喜欢"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:596
|
||||
msgid "Unmute"
|
||||
msgstr "取消静音"
|
||||
msgstr "取消隐藏"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:373
|
||||
msgid "Unmute Account"
|
||||
msgstr "取消静音账户"
|
||||
msgstr "取消隐藏账户"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:171
|
||||
msgid "Unmute thread"
|
||||
msgstr "取消静音讨论串"
|
||||
msgstr "取消隐藏讨论串"
|
||||
|
||||
#: src/view/screens/ProfileFeed.tsx:353
|
||||
#: src/view/screens/ProfileList.tsx:580
|
||||
@@ -4048,7 +4048,7 @@ msgstr "取消固定"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:473
|
||||
msgid "Unpin moderation list"
|
||||
msgstr "取消固定管理员列表"
|
||||
msgstr "取消固定限制列表"
|
||||
|
||||
#: src/view/screens/ProfileFeed.tsx:345
|
||||
msgid "Unsave"
|
||||
@@ -4090,11 +4090,11 @@ msgstr "使用系统默认浏览器"
|
||||
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:155
|
||||
msgid "Use this to sign into the other app along with your handle."
|
||||
msgstr "使用这个和你的昵称一起登录其他应用。"
|
||||
msgstr "使用这个和你的用户识别符一起登录其他应用。"
|
||||
|
||||
#: src/view/com/modals/ServerInput.tsx:105
|
||||
msgid "Use your domain as your Bluesky client service provider"
|
||||
msgstr "使用你的域名作为 Bluesky 客户服务提供商"
|
||||
msgstr "使用你的域名作为 Bluesky 客户端的服务提供方"
|
||||
|
||||
#: src/view/com/modals/InviteCodes.tsx:200
|
||||
msgid "Used by:"
|
||||
@@ -4114,7 +4114,7 @@ msgstr "用户屏蔽了你"
|
||||
|
||||
#: src/view/com/auth/create/Step3.tsx:41
|
||||
msgid "User handle"
|
||||
msgstr "用户昵称"
|
||||
msgstr "用户识别符"
|
||||
|
||||
#: src/view/com/lists/ListCard.tsx:84
|
||||
#: src/view/com/modals/UserAddRemoveLists.tsx:198
|
||||
@@ -4220,7 +4220,7 @@ msgstr "警告"
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124
|
||||
msgid "We also think you'll like \"For You\" by Skygaze:"
|
||||
msgstr "我们认为还你会喜欢 Skygaze 所维护的 \"For You\""
|
||||
msgstr "我们认为还你会喜欢 Skygaze 所维护的 \"For You\":"
|
||||
|
||||
#: src/screens/Deactivated.tsx:134
|
||||
msgid "We estimate {estimatedTime} until your account is ready."
|
||||
@@ -4236,7 +4236,7 @@ msgstr "我们已经看完了你关注的帖子。这是来自 <0/> 的最新消
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:119
|
||||
msgid "We recommend our \"Discover\" feed:"
|
||||
msgstr "我们推荐我们的 \"Discover\" 信息流"
|
||||
msgstr "我们推荐我们的 \"Discover\" 信息流:"
|
||||
|
||||
#: 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."
|
||||
@@ -4329,7 +4329,7 @@ msgstr "XXXXXX"
|
||||
#: src/view/screens/PreferencesThreads.tsx:106
|
||||
#: src/view/screens/PreferencesThreads.tsx:129
|
||||
msgid "Yes"
|
||||
msgstr "是的"
|
||||
msgstr "启用"
|
||||
|
||||
#: src/screens/Deactivated.tsx:131
|
||||
msgid "You are in line."
|
||||
@@ -4378,11 +4378,11 @@ msgstr "你已屏蔽了此用户,你将无法查看他们发布的内容。"
|
||||
#: src/view/com/modals/ChangePassword.tsx:87
|
||||
#: src/view/com/modals/ChangePassword.tsx:121
|
||||
msgid "You have entered an invalid code. It should look like XXXXX-XXXXX."
|
||||
msgstr ""
|
||||
msgstr "你输入的邀请码无效。它应该长得像这样 XXXXX-XXXXX。"
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:87
|
||||
msgid "You have muted this user."
|
||||
msgstr "你已静音这个用户。"
|
||||
msgstr "你已隐藏这个用户。"
|
||||
|
||||
#: src/view/com/feeds/ProfileFeedgens.tsx:136
|
||||
msgid "You have no feeds."
|
||||
@@ -4403,7 +4403,7 @@ msgstr "你尚未创建任何 App 专用密码,可以通过点击下面的按
|
||||
|
||||
#: 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."
|
||||
msgstr "你还没有静音任何账号。要静音账号,请转到其个人资料并在其账号上的菜单中选择 \"静音账号\"。"
|
||||
msgstr "你还没有隐藏任何账号。要隐藏账号,请转到其个人资料并在其账号上的菜单中选择 \"隐藏账号\"。"
|
||||
|
||||
#: src/view/com/modals/ContentFilteringSettings.tsx:170
|
||||
msgid "You must be 18 or older to enable adult content."
|
||||
@@ -4487,11 +4487,11 @@ msgstr "你的关注信息流为空!关注更多用户去看看他们发了什
|
||||
|
||||
#: src/view/com/auth/create/Step3.tsx:45
|
||||
msgid "Your full handle will be"
|
||||
msgstr "你的完整昵称将修改为"
|
||||
msgstr "你的完整用户识别符将修改为"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:270
|
||||
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/shell/desktop/RightNav.tsx:137
|
||||
@@ -4501,7 +4501,7 @@ msgstr "在使用 App 专用密码登录时,你的邀请码将被隐藏"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:155
|
||||
msgid "Your password has been changed successfully!"
|
||||
msgstr ""
|
||||
msgstr "你的密码已更改成功!"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:267
|
||||
msgid "Your post has been published"
|
||||
@@ -4511,7 +4511,7 @@ msgstr "你的帖子已发送"
|
||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:59
|
||||
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:59
|
||||
msgid "Your posts, likes, and blocks are public. Mutes are private."
|
||||
msgstr "你的帖子、点赞和屏蔽是公开可见的,而静音不可见。"
|
||||
msgstr "你的帖子、点赞和屏蔽是公开可见的,而隐藏不可见。"
|
||||
|
||||
#: src/view/com/modals/SwitchAccount.tsx:84
|
||||
#: src/view/screens/Settings.tsx:125
|
||||
@@ -4524,4 +4524,4 @@ msgstr "你的回复已发送"
|
||||
|
||||
#: src/view/com/auth/create/Step3.tsx:28
|
||||
msgid "Your user handle"
|
||||
msgstr "你的用户昵称"
|
||||
msgstr "你的用户识别符"
|
||||
|
||||
@@ -10,9 +10,8 @@ import {pluralize} from '#/lib/strings/helpers'
|
||||
|
||||
import {atoms as a, useTheme, useBreakpoints} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {Text, P} from '#/components/Typography'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {H2, P} from '#/components/Typography'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
@@ -100,10 +99,10 @@ export function Deactivated() {
|
||||
<Logo width={120} />
|
||||
</View>
|
||||
|
||||
<H2 style={[a.pb_sm]}>
|
||||
<Text style={[a.text_4xl, a.font_bold, a.pb_sm]}>
|
||||
<Trans>You're in line</Trans>
|
||||
</H2>
|
||||
<P style={[t.atoms.text_contrast_700]}>
|
||||
</Text>
|
||||
<P style={[t.atoms.text_contrast_medium]}>
|
||||
<Trans>
|
||||
There's been a rush of new users to Bluesky! We'll activate your
|
||||
account as soon as we can.
|
||||
|
||||
@@ -165,7 +165,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
|
||||
isWeb ? a.fixed : a.absolute,
|
||||
{bottom: 0, left: 0, right: 0},
|
||||
t.atoms.bg,
|
||||
t.atoms.border,
|
||||
t.atoms.border_contrast_low,
|
||||
a.border_t,
|
||||
a.align_center,
|
||||
gtMobile ? a.px_5xl : a.px_xl,
|
||||
@@ -227,5 +227,7 @@ export function Description({
|
||||
style,
|
||||
}: React.PropsWithChildren<TextStyleProp>) {
|
||||
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 * as Toggle from '#/components/forms/Toggle'
|
||||
import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed'
|
||||
import {Text, H3} from '#/components/Typography'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {RichText} from '#/components/RichText'
|
||||
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||
@@ -94,14 +94,14 @@ function PrimaryFeedCardInner({
|
||||
</View>
|
||||
|
||||
<View style={[a.pt_xs, a.flex_grow]}>
|
||||
<H3
|
||||
<Text
|
||||
style={[
|
||||
a.text_lg,
|
||||
a.font_bold,
|
||||
ctx.selected && styles.textSelected,
|
||||
]}>
|
||||
{feed.displayName}
|
||||
</H3>
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
@@ -137,7 +137,7 @@ function PrimaryFeedCardInner({
|
||||
},
|
||||
a.mt_md,
|
||||
a.w_full,
|
||||
t.name === 'light' ? t.atoms.border : t.atoms.border_contrast,
|
||||
t.atoms.border_contrast_low,
|
||||
ctx.selected && {
|
||||
borderTopColor: t.palette.white,
|
||||
},
|
||||
@@ -239,14 +239,14 @@ function FeedCardInner({feed}: {feed: FeedSourceInfo; config: FeedConfig}) {
|
||||
</View>
|
||||
|
||||
<View style={[a.pt_2xs, a.flex_grow]}>
|
||||
<H3
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
ctx.selected && styles.textSelected,
|
||||
]}>
|
||||
{feed.displayName}
|
||||
</H3>
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
{opacity: 0.8},
|
||||
@@ -281,7 +281,7 @@ function FeedCardInner({feed}: {feed: FeedSourceInfo; config: FeedConfig}) {
|
||||
},
|
||||
a.mt_md,
|
||||
a.w_full,
|
||||
t.name === 'light' ? t.atoms.border : t.atoms.border_contrast,
|
||||
t.atoms.border_contrast_low,
|
||||
ctx.selected && {
|
||||
borderTopColor: t.palette.primary_200,
|
||||
},
|
||||
@@ -360,7 +360,7 @@ export function FeedCardPlaceholder({primary}: {primary?: boolean}) {
|
||||
},
|
||||
a.mt_md,
|
||||
a.w_full,
|
||||
t.atoms.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
@@ -115,12 +115,22 @@ export function StepAlgoFeeds() {
|
||||
onChange={setPrimaryFeedUris}
|
||||
label={_(msg`Select your primary algorithmic feeds`)}>
|
||||
<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>
|
||||
</Text>
|
||||
<FeedCard config={PRIMARY_FEEDS[0]} />
|
||||
<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>
|
||||
</Text>
|
||||
<FeedCard config={PRIMARY_FEEDS[1]} />
|
||||
@@ -131,7 +141,12 @@ export function StepAlgoFeeds() {
|
||||
onChange={setSeconaryFeedUris}
|
||||
label={_(msg`Select your secondary algorithmic feeds`)}>
|
||||
<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>
|
||||
</Text>
|
||||
<View style={[a.gap_md]}>
|
||||
|
||||
@@ -101,7 +101,7 @@ export function StepFinished() {
|
||||
<Trans>Public</Trans>
|
||||
</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>
|
||||
Your posts, likes, and blocks are public. Mutes are private.
|
||||
</Trans>
|
||||
@@ -115,7 +115,7 @@ export function StepFinished() {
|
||||
<Trans>Open</Trans>
|
||||
</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>
|
||||
</Text>
|
||||
</View>
|
||||
@@ -131,7 +131,7 @@ export function StepFinished() {
|
||||
<Trans>Flexible</Trans>
|
||||
</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>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -96,7 +96,7 @@ export function AdultContentEnabledPref({
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
t.atoms.text_contrast_700,
|
||||
t.atoms.text_contrast_medium,
|
||||
a.leading_snug,
|
||||
{paddingTop: 1},
|
||||
]}>
|
||||
|
||||
@@ -57,7 +57,7 @@ export function ModerationOption({
|
||||
entering={isMounted.current ? FadeIn : undefined}>
|
||||
<View style={[a.gap_xs, {width: '50%'}]}>
|
||||
<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}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -95,7 +95,7 @@ export function SuggestedAccountCard({
|
||||
<Text style={[a.font_bold, a.text_md, a.pb_xs]} numberOfLines={1}>
|
||||
{profile.displayName}
|
||||
</Text>
|
||||
<Text style={[t.atoms.text_contrast_600]}>{profile.handle}</Text>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>{profile.handle}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -124,7 +124,7 @@ export function SuggestedAccountCard({
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
a.w_full,
|
||||
t.name === 'light' ? t.atoms.border : t.atoms.border_contrast,
|
||||
t.atoms.border_contrast_low,
|
||||
ctx.selected && {
|
||||
borderTopColor: t.palette.primary_200,
|
||||
},
|
||||
|
||||
@@ -175,7 +175,11 @@ export function StepSuggestedAccounts() {
|
||||
)}
|
||||
onPress={handleContinue}>
|
||||
<ButtonText>
|
||||
<Trans>Follow All</Trans>
|
||||
{dids.length === 20 ? (
|
||||
<Trans>Follow All</Trans>
|
||||
) : (
|
||||
<Trans>Follow</Trans>
|
||||
)}
|
||||
</ButtonText>
|
||||
<ButtonIcon icon={saving ? Loader : Plus} position="right" />
|
||||
</Button>
|
||||
|
||||
Vendored
+13
-11
@@ -22,15 +22,15 @@ export interface ProfileShadow {
|
||||
blockingUri: string | undefined
|
||||
}
|
||||
|
||||
type ProfileView =
|
||||
| AppBskyActorDefs.ProfileView
|
||||
| AppBskyActorDefs.ProfileViewBasic
|
||||
| AppBskyActorDefs.ProfileViewDetailed
|
||||
|
||||
const shadows: WeakMap<ProfileView, Partial<ProfileShadow>> = new WeakMap()
|
||||
const shadows: WeakMap<
|
||||
AppBskyActorDefs.ProfileView,
|
||||
Partial<ProfileShadow>
|
||||
> = new WeakMap()
|
||||
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 [prevPost, setPrevPost] = useState(profile)
|
||||
if (profile !== prevPost) {
|
||||
@@ -70,10 +70,10 @@ export function updateProfileShadow(
|
||||
})
|
||||
}
|
||||
|
||||
function mergeShadow(
|
||||
profile: ProfileView,
|
||||
function mergeShadow<TProfileView extends AppBskyActorDefs.ProfileView>(
|
||||
profile: TProfileView,
|
||||
shadow: Partial<ProfileShadow>,
|
||||
): Shadow<ProfileView> {
|
||||
): Shadow<TProfileView> {
|
||||
return castAsShadow({
|
||||
...profile,
|
||||
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* findAllProfilesInMyBlockedAccountsQueryData(queryClient, did)
|
||||
yield* findAllProfilesInMyMutedAccountsQueryData(queryClient, did)
|
||||
|
||||
@@ -26,17 +26,6 @@ export interface EditProfileModal {
|
||||
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 {
|
||||
name: 'moderation-details'
|
||||
context: 'account' | 'content'
|
||||
@@ -202,7 +191,6 @@ export type Modal =
|
||||
| ChangeHandleModal
|
||||
| DeleteAccountModal
|
||||
| EditProfileModal
|
||||
| ProfilePreviewModal
|
||||
| BirthDateSettingsModal
|
||||
| VerifyEmailModal
|
||||
| ChangeEmailModal
|
||||
@@ -228,7 +216,6 @@ export type Modal =
|
||||
| AltTextImageModal
|
||||
| CropImageModal
|
||||
| EditImageModal
|
||||
| ServerInputModal
|
||||
| RepostModal
|
||||
| SelfLabelModal
|
||||
| ThreadgateModal
|
||||
|
||||
@@ -112,6 +112,7 @@ export function transform(legacy: Partial<LegacySchema>): Schema {
|
||||
hiddenPosts: defaults.hiddenPosts,
|
||||
externalEmbeds: defaults.externalEmbeds,
|
||||
lastSelectedHomeFeed: defaults.lastSelectedHomeFeed,
|
||||
pdsAddressHistory: defaults.pdsAddressHistory,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ export const schema = z.object({
|
||||
hiddenPosts: z.array(z.string()).optional(), // should move to server
|
||||
useInAppBrowser: z.boolean().optional(),
|
||||
lastSelectedHomeFeed: z.string().optional(),
|
||||
pdsAddressHistory: z.array(z.string()).optional(),
|
||||
})
|
||||
export type Schema = z.infer<typeof schema>
|
||||
|
||||
@@ -91,4 +92,5 @@ export const defaults: Schema = {
|
||||
hiddenPosts: [],
|
||||
useInAppBrowser: undefined,
|
||||
lastSelectedHomeFeed: undefined,
|
||||
pdsAddressHistory: [],
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import chunk from 'lodash.chunk'
|
||||
import {QueryClient} from '@tanstack/react-query'
|
||||
import {getAgent} from '../../session'
|
||||
import {precacheProfile as precacheResolvedUri} from '../resolve-uri'
|
||||
import {precacheProfile} from '../profile'
|
||||
import {NotificationType, FeedNotification, FeedPage} from './types'
|
||||
|
||||
const GROUPABLE_REASONS = ['like', 'repost', 'follow']
|
||||
@@ -59,7 +59,7 @@ export async function fetchPage({
|
||||
if (notif.subjectUri) {
|
||||
notif.subject = subjects.get(notif.subjectUri)
|
||||
if (notif.subject) {
|
||||
precacheResolvedUri(queryClient, notif.subject.author) // precache the handle->did resolution
|
||||
precacheProfile(queryClient, notif.subject.author)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {MergeFeedAPI} from 'lib/api/feed/merge'
|
||||
import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {precacheFeedPosts as precacheResolvedUris} from './resolve-uri'
|
||||
import {precacheFeedPostProfiles} from './profile'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
import {getModerationOpts} from '#/state/queries/preferences/moderation'
|
||||
@@ -138,7 +138,7 @@ export function usePostFeedQuery(
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -10,7 +10,7 @@ import {getAgent} from '#/state/session'
|
||||
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
|
||||
import {findPostInQueryData as findPostInFeedQueryData} from './post-feed'
|
||||
import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed'
|
||||
import {precacheThreadPosts as precacheResolvedUris} from './resolve-uri'
|
||||
import {precacheThreadPostProfiles} from './profile'
|
||||
import {getEmbeddedPost} from './util'
|
||||
|
||||
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!})
|
||||
if (res.success) {
|
||||
const nodes = responseToThreadNodes(res.data.thread)
|
||||
precacheResolvedUris(queryClient, nodes) // precache the handle->did resolution
|
||||
precacheThreadPostProfiles(queryClient, nodes)
|
||||
return nodes
|
||||
}
|
||||
return {type: 'unknown', uri: uri!}
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyActorProfile,
|
||||
AppBskyActorGetProfile,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
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 {STALE} from '#/state/queries'
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {ThreadNode} from './post-thread'
|
||||
|
||||
export const RQKEY = (did: string) => ['profile', did]
|
||||
export const profilesQueryKey = (handles: string[]) => ['profiles', handles]
|
||||
export const profileBasicQueryKey = (didOrHandle: string) => [
|
||||
'profileBasic',
|
||||
didOrHandle,
|
||||
]
|
||||
|
||||
export function useProfileQuery({
|
||||
did,
|
||||
@@ -34,18 +42,26 @@ export function useProfileQuery({
|
||||
did: string | undefined
|
||||
staleTime?: number
|
||||
}) {
|
||||
return useQuery({
|
||||
const queryClient = useQueryClient()
|
||||
return useQuery<AppBskyActorDefs.ProfileViewDetailed>({
|
||||
// WARNING
|
||||
// this staleTime is load-bearing
|
||||
// if you remove it, the UI infinite-loops
|
||||
// -prf
|
||||
staleTime,
|
||||
refetchOnWindowFocus: true,
|
||||
queryKey: RQKEY(did || ''),
|
||||
queryKey: RQKEY(did ?? ''),
|
||||
queryFn: async () => {
|
||||
const res = await getAgent().getProfile({actor: did || ''})
|
||||
const res = await getAgent().getProfile({actor: did ?? ''})
|
||||
return res.data
|
||||
},
|
||||
placeholderData: () => {
|
||||
if (!did) return
|
||||
|
||||
return queryClient.getQueryData<AppBskyActorDefs.ProfileViewBasic>(
|
||||
profileBasicQueryKey(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(
|
||||
actor: string,
|
||||
fn: (res: AppBskyActorGetProfile.Response) => boolean,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {QueryClient, useQuery, UseQueryResult} from '@tanstack/react-query'
|
||||
import {AtUri, AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
|
||||
import {useQuery, useQueryClient, UseQueryResult} from '@tanstack/react-query'
|
||||
import {AtUri, AppBskyActorDefs} from '@atproto/api'
|
||||
|
||||
import {profileBasicQueryKey as RQKEY_PROFILE_BASIC} from './profile'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {ThreadNode} from './post-thread'
|
||||
|
||||
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) {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useQuery<string, Error>({
|
||||
staleTime: STALE.HOURS.ONE,
|
||||
queryKey: RQKEY(didOrHandle || ''),
|
||||
async queryFn() {
|
||||
if (!didOrHandle) {
|
||||
return ''
|
||||
}
|
||||
if (!didOrHandle.startsWith('did:')) {
|
||||
const res = await getAgent().resolveHandle({handle: didOrHandle})
|
||||
didOrHandle = res.data.did
|
||||
}
|
||||
return didOrHandle
|
||||
queryKey: RQKEY(didOrHandle ?? ''),
|
||||
queryFn: async () => {
|
||||
if (!didOrHandle) return ''
|
||||
// Just return the did if it's already one
|
||||
if (didOrHandle.startsWith('did:')) return didOrHandle
|
||||
|
||||
const res = await getAgent().resolveHandle({handle: didOrHandle})
|
||||
return res.data.did
|
||||
},
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -83,19 +83,19 @@ export function HomeLoggedOutCTA() {
|
||||
<View style={[styles.footer, pal.view, pal.border]}>
|
||||
<TextLink
|
||||
type="2xl"
|
||||
href="https://blueskyweb.xyz"
|
||||
href="https://bsky.social"
|
||||
text={_(msg`Business`)}
|
||||
style={[styles.footerLink, pal.link]}
|
||||
/>
|
||||
<TextLink
|
||||
type="2xl"
|
||||
href="https://blueskyweb.xyz/blog"
|
||||
href="https://bsky.social/about/blog"
|
||||
text={_(msg`Blog`)}
|
||||
style={[styles.footerLink, pal.link]}
|
||||
/>
|
||||
<TextLink
|
||||
type="2xl"
|
||||
href="https://blueskyweb.xyz/join"
|
||||
href="https://bsky.social/about/join"
|
||||
text={_(msg`Jobs`)}
|
||||
style={[styles.footerLink, pal.link]}
|
||||
/>
|
||||
|
||||
@@ -102,17 +102,17 @@ function Footer({styles}: {styles: ReturnType<typeof useStyles>}) {
|
||||
return (
|
||||
<View style={[styles.footer, pal.view, pal.border]}>
|
||||
<TextLink
|
||||
href="https://blueskyweb.xyz"
|
||||
href="https://bsky.social"
|
||||
text="Business"
|
||||
style={[styles.footerLink, pal.link]}
|
||||
/>
|
||||
<TextLink
|
||||
href="https://blueskyweb.xyz/blog"
|
||||
href="https://bsky.social/about/blog"
|
||||
text="Blog"
|
||||
style={[styles.footerLink, pal.link]}
|
||||
/>
|
||||
<TextLink
|
||||
href="https://blueskyweb.xyz/join"
|
||||
href="https://bsky.social/about/join"
|
||||
text="Jobs"
|
||||
style={[styles.footerLink, pal.link]}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ActivityIndicator,
|
||||
Keyboard,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native'
|
||||
@@ -13,7 +14,6 @@ import {StepHeader} from './StepHeader'
|
||||
import {s} from 'lib/styles'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {TextInput} from '../util/TextInput'
|
||||
import {Button} from '../../util/forms/Button'
|
||||
import {Policies} from './Policies'
|
||||
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
||||
import {isWeb} from 'platform/detection'
|
||||
@@ -21,7 +21,14 @@ import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
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 {
|
||||
if (!date || date.toString() === 'Invalid Date') {
|
||||
@@ -43,16 +50,12 @@ export function Step1({
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {openModal} = useModalControls()
|
||||
const serverInputControl = useDialogControl()
|
||||
|
||||
const onPressSelectService = React.useCallback(() => {
|
||||
openModal({
|
||||
name: 'server-input',
|
||||
initialService: uiState.serviceUrl,
|
||||
onSelect: (url: string) =>
|
||||
uiDispatch({type: 'set-service-url', value: url}),
|
||||
})
|
||||
serverInputControl.open()
|
||||
Keyboard.dismiss()
|
||||
}, [uiDispatch, uiState.serviceUrl, openModal])
|
||||
}, [serverInputControl])
|
||||
|
||||
const onPressWaitlist = React.useCallback(() => {
|
||||
openModal({name: 'waitlist'})
|
||||
@@ -64,23 +67,72 @@ export function Step1({
|
||||
|
||||
return (
|
||||
<View>
|
||||
<StepHeader uiState={uiState} title={_(msg`Your account`)}>
|
||||
<View>
|
||||
<Button
|
||||
testID="selectServiceButton"
|
||||
type="default"
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
accessibilityLabel={_(msg`Select service`)}
|
||||
accessibilityHint={_(msg`Sets server for the Bluesky client`)}
|
||||
onPress={onPressSelectService}>
|
||||
<FontAwesomeIcon icon="server" size={21} color={pal.colors.text} />
|
||||
</Button>
|
||||
<ServerInputDialog
|
||||
control={serverInputControl}
|
||||
onSelect={url => uiDispatch({type: 'set-service-url', value: url})}
|
||||
/>
|
||||
<StepHeader uiState={uiState} title={_(msg`Your account`)} />
|
||||
|
||||
<View style={s.pb20}>
|
||||
<Text type="md-medium" style={[pal.text, s.mb2]}>
|
||||
<Trans>Hosting provider</Trans>
|
||||
</Text>
|
||||
<View style={[pal.border, {borderWidth: 1, borderRadius: 6}]}>
|
||||
<View
|
||||
style={[
|
||||
pal.borderDark,
|
||||
{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>
|
||||
</StepHeader>
|
||||
</View>
|
||||
|
||||
{!uiState.serviceDescription ? (
|
||||
<ActivityIndicator />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Keyboard,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
@@ -24,7 +25,9 @@ import {logger} from '#/logger'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {styles} from './styles'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
|
||||
import {ServerInputDialog} from '../server-input'
|
||||
|
||||
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||
|
||||
@@ -51,19 +54,16 @@ export const ForgotPasswordForm = ({
|
||||
const [email, setEmail] = useState<string>('')
|
||||
const {screen} = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {openModal} = useModalControls()
|
||||
const serverInputControl = useDialogControl()
|
||||
|
||||
useEffect(() => {
|
||||
screen('Signin:ForgotPassword')
|
||||
}, [screen])
|
||||
|
||||
const onPressSelectService = () => {
|
||||
openModal({
|
||||
name: 'server-input',
|
||||
initialService: serviceUrl,
|
||||
onSelect: setServiceUrl,
|
||||
})
|
||||
}
|
||||
const onPressSelectService = React.useCallback(() => {
|
||||
serverInputControl.open()
|
||||
Keyboard.dismiss()
|
||||
}, [serverInputControl])
|
||||
|
||||
const onPressNext = async () => {
|
||||
if (!EmailValidator.validate(email)) {
|
||||
@@ -96,6 +96,10 @@ export const ForgotPasswordForm = ({
|
||||
return (
|
||||
<>
|
||||
<View>
|
||||
<ServerInputDialog
|
||||
control={serverInputControl}
|
||||
onSelect={setServiceUrl}
|
||||
/>
|
||||
<Text type="title-lg" style={[pal.text, styles.screenTitle]}>
|
||||
<Trans>Reset password</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -25,7 +25,9 @@ import {logger} from '#/logger'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {styles} from './styles'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
|
||||
import {ServerInputDialog} from '../server-input'
|
||||
|
||||
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||
|
||||
@@ -58,15 +60,11 @@ export const LoginForm = ({
|
||||
const [password, setPassword] = useState<string>('')
|
||||
const passwordInputRef = useRef<TextInput>(null)
|
||||
const {_} = useLingui()
|
||||
const {openModal} = useModalControls()
|
||||
const {login} = useSessionApi()
|
||||
const serverInputControl = useDialogControl()
|
||||
|
||||
const onPressSelectService = () => {
|
||||
openModal({
|
||||
name: 'server-input',
|
||||
initialService: serviceUrl,
|
||||
onSelect: setServiceUrl,
|
||||
})
|
||||
serverInputControl.open()
|
||||
Keyboard.dismiss()
|
||||
track('Signin:PressedSelectService')
|
||||
}
|
||||
@@ -130,6 +128,11 @@ export const LoginForm = ({
|
||||
const isReady = !!serviceDescription && !!identifier && !!password
|
||||
return (
|
||||
<View testID="loginForm">
|
||||
<ServerInputDialog
|
||||
control={serverInputControl}
|
||||
onSelect={setServiceUrl}
|
||||
/>
|
||||
|
||||
<Text type="sm-bold" style={[pal.text, styles.groupLabel]}>
|
||||
<Trans>Sign into</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -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 {downloadAndResize} from 'lib/media/manip'
|
||||
import {POST_IMG_MAX} from 'lib/constants'
|
||||
import {isIOS} from 'platform/detection'
|
||||
|
||||
export interface TextInputRef {
|
||||
focus: () => void
|
||||
@@ -252,6 +253,7 @@ const styles = StyleSheet.create({
|
||||
fontSize: 18,
|
||||
letterSpacing: 0.2,
|
||||
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 (
|
||||
<>
|
||||
<View style={styles.container}>
|
||||
<EditorContent editor={editor} />
|
||||
<EditorContent
|
||||
editor={editor}
|
||||
style={{color: pal.text.color as string}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{isDropping && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
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 {ScrollView} from './util'
|
||||
import {s, colors, gradients} from 'lib/styles'
|
||||
@@ -129,6 +129,10 @@ function AdultContentEnabledPref() {
|
||||
}
|
||||
}, [variables, preferences, mutate, _])
|
||||
|
||||
const onAdultContentLinkPress = React.useCallback(() => {
|
||||
Linking.openURL('https://bsky.app/')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={s.mb10}>
|
||||
{isIOS ? (
|
||||
@@ -138,8 +142,9 @@ function AdultContentEnabledPref() {
|
||||
Adult content can only be enabled via the Web at{' '}
|
||||
<TextLink
|
||||
style={pal.link}
|
||||
href="https://bsky.app"
|
||||
href=""
|
||||
text="bsky.app"
|
||||
onPress={onAdultContentLinkPress}
|
||||
/>
|
||||
.
|
||||
</Trans>
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import React, {useRef, useEffect} from 'react'
|
||||
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 {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
||||
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 {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import * as ConfirmModal from './Confirm'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
import * as ProfilePreviewModal from './ProfilePreview'
|
||||
import * as ServerInputModal from './ServerInput'
|
||||
import * as RepostModal from './Repost'
|
||||
import * as SelfLabelModal from './SelfLabel'
|
||||
import * as ThreadgateModal from './Threadgate'
|
||||
@@ -50,34 +44,14 @@ export function ModalsContainer() {
|
||||
const {closeModal} = useModalControls()
|
||||
const bottomSheetRef = useRef<BottomSheet>(null)
|
||||
const pal = usePalette('default')
|
||||
const safeAreaInsets = useSafeAreaInsets()
|
||||
|
||||
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) => {
|
||||
if (snapPoint === -1) {
|
||||
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 = () => {
|
||||
bottomSheetRef.current?.close()
|
||||
closeModal()
|
||||
@@ -91,7 +65,6 @@ export function ModalsContainer() {
|
||||
}
|
||||
}, [isModalActive, bottomSheetRef, activeModal?.name])
|
||||
|
||||
let needsSafeTopInset = false
|
||||
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
|
||||
let element
|
||||
if (activeModal?.name === 'confirm') {
|
||||
@@ -100,13 +73,6 @@ export function ModalsContainer() {
|
||||
} else if (activeModal?.name === 'edit-profile') {
|
||||
snapPoints = EditProfileModal.snapPoints
|
||||
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') {
|
||||
snapPoints = ReportModal.snapPoints
|
||||
element = <ReportModal.Component {...activeModal} />
|
||||
@@ -200,12 +166,10 @@ export function ModalsContainer() {
|
||||
)
|
||||
}
|
||||
|
||||
const topInset = needsSafeTopInset ? safeAreaInsets.top - HANDLE_HEIGHT : 0
|
||||
return (
|
||||
<BottomSheet
|
||||
ref={bottomSheetRef}
|
||||
snapPoints={snapPoints}
|
||||
topInset={topInset}
|
||||
handleHeight={HANDLE_HEIGHT}
|
||||
index={isModalActive ? 0 : -1}
|
||||
enablePanDownToClose
|
||||
@@ -216,7 +180,6 @@ export function ModalsContainer() {
|
||||
}
|
||||
handleIndicatorStyle={{backgroundColor: pal.text.color}}
|
||||
handleStyle={[styles.handle, pal.view]}
|
||||
onAnimate={onBottomSheetAnimate}
|
||||
onChange={onBottomSheetChange}>
|
||||
{element}
|
||||
</BottomSheet>
|
||||
|
||||
@@ -9,8 +9,6 @@ import {useModals, useModalControls} from '#/state/modals'
|
||||
import type {Modal as ModalIface} from '#/state/modals'
|
||||
import * as ConfirmModal from './Confirm'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
import * as ProfilePreviewModal from './ProfilePreview'
|
||||
import * as ServerInputModal from './ServerInput'
|
||||
import * as ReportModal from './report/Modal'
|
||||
import * as AppealLabelModal from './AppealLabel'
|
||||
import * as CreateOrEditListModal from './CreateOrEditList'
|
||||
@@ -85,10 +83,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||
element = <ConfirmModal.Component {...modal} />
|
||||
} else if (modal.name === 'edit-profile') {
|
||||
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') {
|
||||
element = <ReportModal.Component {...modal} />
|
||||
} else if (modal.name === 'appeal-label') {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
@@ -18,7 +18,7 @@ import {useLingui} from '@lingui/react'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
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]
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export const Pager = React.forwardRef(function PagerImpl(
|
||||
const anchorRef = React.useRef(null)
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
setPage: (index: number) => setSelectedPage(index),
|
||||
setPage: (index: number) => onTabBarSelect(index),
|
||||
}))
|
||||
|
||||
const onTabBarSelect = React.useCallback(
|
||||
|
||||
@@ -61,26 +61,19 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
const headerHeight = headerOnlyHeight + tabBarHeight
|
||||
|
||||
// capture the header bar sizing
|
||||
const onTabBarLayout = React.useCallback(
|
||||
(evt: LayoutChangeEvent) => {
|
||||
const height = evt.nativeEvent.layout.height
|
||||
if (height > 0) {
|
||||
// The rounding is necessary to prevent jumps on iOS
|
||||
setTabBarHeight(Math.round(height))
|
||||
}
|
||||
},
|
||||
[setTabBarHeight],
|
||||
)
|
||||
const onHeaderOnlyLayout = React.useCallback(
|
||||
(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 onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
|
||||
const height = evt.nativeEvent.layout.height
|
||||
if (height > 0) {
|
||||
// The rounding is necessary to prevent jumps on iOS
|
||||
setTabBarHeight(Math.round(height))
|
||||
}
|
||||
})
|
||||
const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => {
|
||||
if (height > 0) {
|
||||
// The rounding is necessary to prevent jumps on iOS
|
||||
setHeaderOnlyHeight(Math.round(height))
|
||||
}
|
||||
})
|
||||
|
||||
const renderTabBar = React.useCallback(
|
||||
(props: RenderTabBarFnProps) => {
|
||||
@@ -228,7 +221,7 @@ let PagerTabBar = ({
|
||||
testID?: string
|
||||
scrollY: SharedValue<number>
|
||||
renderHeader?: () => JSX.Element
|
||||
onHeaderOnlyLayout: (e: LayoutChangeEvent) => void
|
||||
onHeaderOnlyLayout: (height: number) => void
|
||||
onTabBarLayout: (e: LayoutChangeEvent) => void
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
onSelect?: (index: number) => void
|
||||
@@ -240,12 +233,40 @@ let PagerTabBar = ({
|
||||
},
|
||||
],
|
||||
}))
|
||||
const pendingHeaderHeight = React.useRef<null | number>(null)
|
||||
return (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
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?.()}
|
||||
{
|
||||
// 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
|
||||
onLayout={onTabBarLayout}
|
||||
|
||||
@@ -31,6 +31,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
children,
|
||||
testID,
|
||||
items,
|
||||
isHeaderReady,
|
||||
renderHeader,
|
||||
initialPage,
|
||||
onPageSelected,
|
||||
@@ -46,6 +47,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
<PagerTabBar
|
||||
items={items}
|
||||
renderHeader={renderHeader}
|
||||
isHeaderReady={isHeaderReady}
|
||||
currentPage={currentPage}
|
||||
onCurrentPageSelected={onCurrentPageSelected}
|
||||
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(
|
||||
@@ -80,8 +89,14 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
{toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child, i) => {
|
||||
const isReady = isHeaderReady
|
||||
return (
|
||||
<View key={i} collapsable={false}>
|
||||
<View
|
||||
key={i}
|
||||
collapsable={false}
|
||||
style={{
|
||||
display: isReady ? undefined : 'none',
|
||||
}}>
|
||||
<PagerItem isFocused={i === currentPage} renderTab={child} />
|
||||
</View>
|
||||
)
|
||||
@@ -94,6 +109,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
let PagerTabBar = ({
|
||||
currentPage,
|
||||
items,
|
||||
isHeaderReady,
|
||||
testID,
|
||||
renderHeader,
|
||||
onCurrentPageSelected,
|
||||
@@ -104,6 +120,7 @@ let PagerTabBar = ({
|
||||
items: string[]
|
||||
testID?: string
|
||||
renderHeader?: () => JSX.Element
|
||||
isHeaderReady: boolean
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
onSelect?: (index: number) => void
|
||||
tabBarAnchor?: JSX.Element | null | undefined
|
||||
@@ -112,7 +129,12 @@ let PagerTabBar = ({
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
return (
|
||||
<>
|
||||
<View style={[!isMobile && styles.headerContainerDesktop, pal.border]}>
|
||||
<View
|
||||
style={[
|
||||
!isMobile && styles.headerContainerDesktop,
|
||||
pal.border,
|
||||
!isHeaderReady && styles.loadingHeader,
|
||||
]}>
|
||||
{renderHeader?.()}
|
||||
</View>
|
||||
{tabBarAnchor}
|
||||
@@ -123,6 +145,9 @@ let PagerTabBar = ({
|
||||
? styles.tabBarContainerMobile
|
||||
: styles.tabBarContainerDesktop,
|
||||
pal.border,
|
||||
{
|
||||
display: isHeaderReady ? undefined : 'none',
|
||||
},
|
||||
]}>
|
||||
<TabBar
|
||||
testID={testID}
|
||||
@@ -183,6 +208,9 @@ const styles = StyleSheet.create({
|
||||
paddingLeft: 14,
|
||||
paddingRight: 14,
|
||||
},
|
||||
loadingHeader: {
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
})
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@atproto/api'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn'
|
||||
import {Link, TextLink} from '../util/Link'
|
||||
import {RichText} from '../util/text/RichText'
|
||||
import {Text} from '../util/text/Text'
|
||||
@@ -23,7 +24,6 @@ import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
|
||||
import {PostMeta} from '../util/PostMeta'
|
||||
import {PostEmbeds} from '../util/post-embeds'
|
||||
import {PostCtrls} from '../util/post-ctrls/PostCtrls'
|
||||
import {PostDropdownBtn} from '../util/forms/PostDropdownBtn'
|
||||
import {PostHider} from '../util/moderation/PostHider'
|
||||
import {ContentHider} from '../util/moderation/ContentHider'
|
||||
import {PostAlerts} from '../util/moderation/PostAlerts'
|
||||
@@ -31,7 +31,6 @@ import {PostSandboxWarning} from '../util/PostSandboxWarning'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {formatCount} from '../util/numeric/format'
|
||||
import {TimeElapsed} from 'view/com/util/TimeElapsed'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
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 {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
|
||||
import {ThreadPost} from '#/state/queries/post-thread'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSession} from 'state/session'
|
||||
import {WhoCanReply} from '../threadgate/WhoCanReply'
|
||||
|
||||
export function PostThreadItem({
|
||||
@@ -115,7 +114,6 @@ export function PostThreadItem({
|
||||
}
|
||||
|
||||
function PostThreadItemDeleted() {
|
||||
const styles = useStyles()
|
||||
const pal = usePalette('default')
|
||||
return (
|
||||
<View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}>
|
||||
@@ -162,11 +160,10 @@ let PostThreadItemLoaded = ({
|
||||
const {_} = useLingui()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {currentAccount} = useSession()
|
||||
const [limitLines, setLimitLines] = React.useState(
|
||||
() => countLines(richText?.text) >= MAX_POST_LINES,
|
||||
)
|
||||
const styles = useStyles()
|
||||
const {currentAccount} = useSession()
|
||||
const hasEngagement = post.likeCount || post.repostCount
|
||||
|
||||
const rootUri = record.reply?.root?.uri || post.uri
|
||||
@@ -188,9 +185,6 @@ let PostThreadItemLoaded = ({
|
||||
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
|
||||
}, [post.uri, post.author])
|
||||
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(
|
||||
record?.text || '',
|
||||
@@ -255,7 +249,7 @@ let PostThreadItemLoaded = ({
|
||||
style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
|
||||
accessible={false}>
|
||||
<PostSandboxWarning />
|
||||
<View style={styles.layout}>
|
||||
<View style={[styles.layout]}>
|
||||
<View style={[styles.layoutAvi, {paddingBottom: 8}]}>
|
||||
<PreviewableUserAvatar
|
||||
size={42}
|
||||
@@ -268,33 +262,18 @@ let PostThreadItemLoaded = ({
|
||||
<View style={styles.layoutContent}>
|
||||
<View
|
||||
style={[styles.meta, styles.metaExpandedLine1, {zIndex: 1}]}>
|
||||
<View style={[s.flexRow]}>
|
||||
<Link
|
||||
style={styles.metaItem}
|
||||
href={authorHref}
|
||||
title={authorTitle}>
|
||||
<Text
|
||||
type="xl-bold"
|
||||
style={[pal.text]}
|
||||
numberOfLines={1}
|
||||
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)}>
|
||||
· {timeElapsed}
|
||||
</Text>
|
||||
<Link style={s.flex1} href={authorHref} title={authorTitle}>
|
||||
<Text
|
||||
type="xl-bold"
|
||||
style={[pal.text]}
|
||||
numberOfLines={1}
|
||||
lineHeight={1.2}>
|
||||
{sanitizeDisplayName(
|
||||
post.author.displayName ||
|
||||
sanitizeHandle(post.author.handle),
|
||||
)}
|
||||
</TimeElapsed>
|
||||
</View>
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
<View style={styles.meta}>
|
||||
{isAuthorMuted && (
|
||||
@@ -321,33 +300,16 @@ let PostThreadItemLoaded = ({
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<Link
|
||||
style={styles.metaItem}
|
||||
href={authorHref}
|
||||
title={authorTitle}>
|
||||
<Link style={s.flex1} href={authorHref} title={authorTitle}>
|
||||
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
|
||||
{sanitizeHandle(post.author.handle, '@')}
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
<PostDropdownBtn
|
||||
testID="postDropdownBtn"
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
{currentAccount?.did !== post.author.did && (
|
||||
<PostThreadFollowBtn did={post.author.did} />
|
||||
)}
|
||||
</View>
|
||||
<View style={[s.pl10, s.pr10, s.pb10]}>
|
||||
<ContentHider
|
||||
@@ -437,7 +399,7 @@ let PostThreadItemLoaded = ({
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<View style={[s.pl10, s.pb5]}>
|
||||
<View style={[s.pl10, s.pr10, s.pb5]}>
|
||||
<PostCtrls
|
||||
big
|
||||
post={post}
|
||||
@@ -649,7 +611,6 @@ function PostOuterWrapper({
|
||||
}>) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const pal = usePalette('default')
|
||||
const styles = useStyles()
|
||||
if (treeView && depth > 0) {
|
||||
return (
|
||||
<View
|
||||
@@ -726,94 +687,84 @@ function ExpandedPostDetails({
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = () => {
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
return StyleSheet.create({
|
||||
outer: {
|
||||
borderTopWidth: 1,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
outerHighlighted: {
|
||||
paddingTop: 16,
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8,
|
||||
},
|
||||
noTopBorder: {
|
||||
borderTopWidth: 0,
|
||||
},
|
||||
layout: {
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
layoutAvi: {},
|
||||
layoutContent: {
|
||||
flex: 1,
|
||||
paddingRight: 10,
|
||||
},
|
||||
meta: {
|
||||
flexDirection: 'row',
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
},
|
||||
metaExpandedLine1: {
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
metaItem: {
|
||||
paddingRight: 5,
|
||||
maxWidth: isDesktop ? 380 : 220,
|
||||
},
|
||||
alert: {
|
||||
marginBottom: 6,
|
||||
},
|
||||
postTextContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
paddingBottom: 4,
|
||||
paddingRight: 10,
|
||||
},
|
||||
postTextLargeContainer: {
|
||||
paddingHorizontal: 0,
|
||||
paddingRight: 0,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
translateLink: {
|
||||
marginBottom: 6,
|
||||
},
|
||||
contentHider: {
|
||||
marginBottom: 6,
|
||||
},
|
||||
contentHiderChild: {
|
||||
marginTop: 6,
|
||||
},
|
||||
expandedInfo: {
|
||||
flexDirection: 'row',
|
||||
padding: 10,
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
marginTop: 5,
|
||||
marginBottom: 15,
|
||||
},
|
||||
expandedInfoItem: {
|
||||
marginRight: 10,
|
||||
},
|
||||
loadMore: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
gap: 4,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
replyLine: {
|
||||
width: 2,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
cursor: {
|
||||
// @ts-ignore web only
|
||||
cursor: 'pointer',
|
||||
},
|
||||
})
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
borderTopWidth: 1,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
outerHighlighted: {
|
||||
paddingTop: 16,
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8,
|
||||
},
|
||||
noTopBorder: {
|
||||
borderTopWidth: 0,
|
||||
},
|
||||
layout: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
layoutAvi: {},
|
||||
layoutContent: {
|
||||
flex: 1,
|
||||
marginLeft: 10,
|
||||
},
|
||||
meta: {
|
||||
flexDirection: 'row',
|
||||
paddingVertical: 2,
|
||||
},
|
||||
metaExpandedLine1: {
|
||||
paddingVertical: 0,
|
||||
},
|
||||
alert: {
|
||||
marginBottom: 6,
|
||||
},
|
||||
postTextContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
paddingBottom: 4,
|
||||
paddingRight: 10,
|
||||
},
|
||||
postTextLargeContainer: {
|
||||
paddingHorizontal: 0,
|
||||
paddingRight: 0,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
translateLink: {
|
||||
marginBottom: 6,
|
||||
},
|
||||
contentHider: {
|
||||
marginBottom: 6,
|
||||
},
|
||||
contentHiderChild: {
|
||||
marginTop: 6,
|
||||
},
|
||||
expandedInfo: {
|
||||
flexDirection: 'row',
|
||||
padding: 10,
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
marginTop: 5,
|
||||
marginBottom: 15,
|
||||
},
|
||||
expandedInfoItem: {
|
||||
marginRight: 10,
|
||||
},
|
||||
loadMore: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
gap: 4,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
replyLine: {
|
||||
width: 2,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
cursor: {
|
||||
// @ts-ignore web only
|
||||
cursor: 'pointer',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {memo} from 'react'
|
||||
import React, {memo, useMemo} from 'react'
|
||||
import {
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
@@ -10,7 +10,8 @@ import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
ProfileModeration,
|
||||
ModerationOpts,
|
||||
moderateProfile,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
@@ -43,79 +44,57 @@ import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
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 {pluralize} from 'lib/strings/helpers'
|
||||
import {toShareUrl} from 'lib/strings/url-helpers'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {shareUrl} from 'lib/sharing'
|
||||
import {s, colors} from 'lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
import {useSession, getAgent} from '#/state/session'
|
||||
import {useSession} from '#/state/session'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {useRequireAuth} from '#/state/session'
|
||||
import {LabelInfo} from '../util/moderation/LabelInfo'
|
||||
import {useProfileShadow} from 'state/cache/profile-shadow'
|
||||
|
||||
interface Props {
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> | null
|
||||
moderation: ProfileModeration | null
|
||||
hideBackButton?: boolean
|
||||
isProfilePreview?: boolean
|
||||
}
|
||||
|
||||
export function ProfileHeader({
|
||||
profile,
|
||||
moderation,
|
||||
hideBackButton = false,
|
||||
isProfilePreview,
|
||||
}: Props) {
|
||||
let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
|
||||
const pal = usePalette('default')
|
||||
|
||||
// loading
|
||||
// =
|
||||
if (!profile || !moderation) {
|
||||
return (
|
||||
<View style={pal.view}>
|
||||
<LoadingPlaceholder width="100%" height={153} />
|
||||
<View
|
||||
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
|
||||
<LoadingPlaceholder width={80} height={80} style={styles.br40} />
|
||||
</View>
|
||||
<View style={styles.content}>
|
||||
<View style={[styles.buttonsLine]}>
|
||||
<LoadingPlaceholder width={167} height={31} style={styles.br50} />
|
||||
</View>
|
||||
return (
|
||||
<View style={pal.view}>
|
||||
<LoadingPlaceholder width="100%" height={150} style={{borderRadius: 0}} />
|
||||
<View
|
||||
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
|
||||
<LoadingPlaceholder width={80} height={80} style={styles.br40} />
|
||||
</View>
|
||||
<View style={styles.content}>
|
||||
<View style={[styles.buttonsLine]}>
|
||||
<LoadingPlaceholder width={167} height={31} style={styles.br50} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
// loaded
|
||||
// =
|
||||
return (
|
||||
<ProfileHeaderLoaded
|
||||
profile={profile}
|
||||
moderation={moderation}
|
||||
hideBackButton={hideBackButton}
|
||||
isProfilePreview={isProfilePreview}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
ProfileHeaderLoading = memo(ProfileHeaderLoading)
|
||||
export {ProfileHeaderLoading}
|
||||
|
||||
interface LoadedProps {
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
|
||||
moderation: ProfileModeration
|
||||
interface Props {
|
||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||
descriptionRT: RichTextAPI | null
|
||||
moderationOpts: ModerationOpts
|
||||
hideBackButton?: boolean
|
||||
isProfilePreview?: boolean
|
||||
isPlaceholderProfile?: boolean
|
||||
}
|
||||
|
||||
let ProfileHeaderLoaded = ({
|
||||
profile,
|
||||
moderation,
|
||||
let ProfileHeader = ({
|
||||
profile: profileUnshadowed,
|
||||
descriptionRT,
|
||||
moderationOpts,
|
||||
hideBackButton = false,
|
||||
isProfilePreview,
|
||||
}: LoadedProps): React.ReactNode => {
|
||||
isPlaceholderProfile,
|
||||
}: Props): React.ReactNode => {
|
||||
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
|
||||
useProfileShadow(profileUnshadowed)
|
||||
const pal = usePalette('default')
|
||||
const palInverted = usePalette('inverted')
|
||||
const {currentAccount, hasSession} = useSession()
|
||||
@@ -132,37 +111,10 @@ let ProfileHeaderLoaded = ({
|
||||
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
|
||||
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
/*
|
||||
* BEGIN handle bio facet resolution
|
||||
*/
|
||||
// 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,
|
||||
const moderation = useMemo(
|
||||
() => moderateProfile(profile, moderationOpts),
|
||||
[profile, moderationOpts],
|
||||
)
|
||||
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(() => {
|
||||
queryClient.invalidateQueries({
|
||||
@@ -443,9 +395,17 @@ let ProfileHeaderLoaded = ({
|
||||
const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower')
|
||||
|
||||
return (
|
||||
<View style={pal.view} pointerEvents="box-none">
|
||||
<View style={[pal.view]} pointerEvents="box-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 style={styles.content} pointerEvents="box-none">
|
||||
<View style={[styles.buttonsLine]} pointerEvents="box-none">
|
||||
@@ -479,7 +439,7 @@ let ProfileHeaderLoaded = ({
|
||||
)
|
||||
) : !profile.viewer?.blockedBy ? (
|
||||
<>
|
||||
{!isProfilePreview && hasSession && (
|
||||
{hasSession && (
|
||||
<TouchableOpacity
|
||||
testID="suggestedFollowsBtn"
|
||||
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)}
|
||||
@@ -598,7 +558,7 @@ let ProfileHeaderLoaded = ({
|
||||
{invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
|
||||
</ThemedText>
|
||||
</View>
|
||||
{!blockHide && (
|
||||
{!isPlaceholderProfile && !blockHide && (
|
||||
<>
|
||||
<View style={styles.metricsLine} pointerEvents="box-none">
|
||||
<Link
|
||||
@@ -649,7 +609,7 @@ let ProfileHeaderLoaded = ({
|
||||
</Text>
|
||||
</View>
|
||||
{descriptionRT && !moderation.profile.blur ? (
|
||||
<View pointerEvents={isNative ? 'auto' : 'none'}>
|
||||
<View pointerEvents="auto">
|
||||
<RichText
|
||||
testID="profileHeaderDescription"
|
||||
style={[styles.description, pal.text]}
|
||||
@@ -667,7 +627,7 @@ let ProfileHeaderLoaded = ({
|
||||
<ProfileHeaderModCard />
|
||||
</View>
|
||||
|
||||
{!isProfilePreview && showSuggestedFollows && (
|
||||
{showSuggestedFollows && (
|
||||
<ProfileHeaderSuggestedFollows
|
||||
actorDid={profile.did}
|
||||
requestDismiss={() => {
|
||||
@@ -714,7 +674,8 @@ let ProfileHeaderLoaded = ({
|
||||
</View>
|
||||
)
|
||||
}
|
||||
ProfileHeaderLoaded = memo(ProfileHeaderLoaded)
|
||||
ProfileHeader = memo(ProfileHeader)
|
||||
export {ProfileHeader}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
banner: {
|
||||
|
||||
@@ -123,6 +123,7 @@ let UserAvatar = ({
|
||||
usePlainRNImage = false,
|
||||
}: UserAvatarProps): React.ReactNode => {
|
||||
const pal = usePalette('default')
|
||||
const backgroundColor = pal.colors.backgroundLight
|
||||
|
||||
const aviStyle = useMemo(() => {
|
||||
if (type === 'algo' || type === 'list') {
|
||||
@@ -130,14 +131,16 @@ let UserAvatar = ({
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size > 32 ? 8 : 3,
|
||||
backgroundColor,
|
||||
}
|
||||
}
|
||||
return {
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: Math.floor(size / 2),
|
||||
backgroundColor,
|
||||
}
|
||||
}, [type, size])
|
||||
}, [type, size, backgroundColor])
|
||||
|
||||
const alert = useMemo(() => {
|
||||
if (!moderation?.alert) {
|
||||
|
||||
@@ -3,7 +3,10 @@ import {StyleSheet, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {ModerationUI} from '@atproto/api'
|
||||
import {Image} from 'expo-image'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {colors} from 'lib/styles'
|
||||
import {useTheme} from 'lib/ThemeContext'
|
||||
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
||||
import {
|
||||
usePhotoLibraryPermission,
|
||||
@@ -13,8 +16,6 @@ import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {isWeb, isAndroid} from 'platform/detection'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {NativeDropdown, DropdownItem} from './forms/NativeDropdown'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
|
||||
export function UserBanner({
|
||||
banner,
|
||||
@@ -26,6 +27,7 @@ export function UserBanner({
|
||||
onSelectNewBanner?: (img: RNImage | null) => void
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const theme = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||
@@ -142,7 +144,10 @@ export function UserBanner({
|
||||
!((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
|
||||
<Image
|
||||
testID="userBannerImage"
|
||||
style={styles.bannerImage}
|
||||
style={[
|
||||
styles.bannerImage,
|
||||
{backgroundColor: theme.palette.default.backgroundLight},
|
||||
]}
|
||||
resizeMode="cover"
|
||||
source={{uri: banner}}
|
||||
blurRadius={moderation?.blur ? 100 : 0}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from 'react'
|
||||
import {Pressable, StyleProp, ViewStyle} from 'react-native'
|
||||
import {StyleProp, ViewStyle} from 'react-native'
|
||||
import {Link} from './Link'
|
||||
import {isAndroid, isWeb} from 'platform/detection'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {usePrefetchProfileQuery} from '#/state/queries/profile'
|
||||
|
||||
interface UserPreviewLinkProps {
|
||||
@@ -14,38 +13,19 @@ interface UserPreviewLinkProps {
|
||||
export function UserPreviewLink(
|
||||
props: React.PropsWithChildren<UserPreviewLinkProps>,
|
||||
) {
|
||||
const {openModal} = useModalControls()
|
||||
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 (
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
openModal({
|
||||
name: 'profile-preview',
|
||||
did: props.did,
|
||||
})
|
||||
}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={props.handle}
|
||||
accessibilityHint=""
|
||||
<Link
|
||||
onPointerEnter={() => {
|
||||
if (isWeb) {
|
||||
prefetchProfileQuery(props.did)
|
||||
}
|
||||
}}
|
||||
href={makeProfileLink(props)}
|
||||
title={props.handle}
|
||||
asAnchor
|
||||
style={props.style}>
|
||||
{props.children}
|
||||
</Pressable>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {Button} from '../forms/Button'
|
||||
import {CenteredView} from '../Views'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
|
||||
export function ErrorScreen({
|
||||
title,
|
||||
@@ -18,66 +20,72 @@ export function ErrorScreen({
|
||||
details,
|
||||
onPressTryAgain,
|
||||
testID,
|
||||
showHeader,
|
||||
}: {
|
||||
title: string
|
||||
message: string
|
||||
details?: string
|
||||
onPressTryAgain?: () => void
|
||||
testID?: string
|
||||
showHeader?: boolean
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<CenteredView testID={testID} style={[styles.outer, pal.view]}>
|
||||
<View style={styles.errorIconContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.errorIcon,
|
||||
{backgroundColor: theme.palette.inverted.background},
|
||||
]}>
|
||||
<FontAwesomeIcon
|
||||
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`,
|
||||
)}>
|
||||
<>
|
||||
{showHeader && isMobile && <ViewHeader title="Error" showBorder />}
|
||||
<CenteredView testID={testID} style={[styles.outer, pal.view]}>
|
||||
<View style={styles.errorIconContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.errorIcon,
|
||||
{backgroundColor: theme.palette.inverted.background},
|
||||
]}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrows-rotate"
|
||||
style={pal.link as FontAwesomeIconStyle}
|
||||
size={16}
|
||||
icon="exclamation"
|
||||
style={pal.textInverted as FontAwesomeIconStyle}
|
||||
size={24}
|
||||
/>
|
||||
<Text type="button" style={[styles.btnText, pal.link]}>
|
||||
<Trans context="action">Try again</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</CenteredView>
|
||||
<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
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AtUri,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {Text} from '../text/Text'
|
||||
@@ -30,6 +31,10 @@ import {Shadow} from '#/state/cache/types'
|
||||
import {useRequireAuth} from '#/state/session'
|
||||
import {msg} from '@lingui/macro'
|
||||
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 = ({
|
||||
big,
|
||||
@@ -116,11 +121,18 @@ let PostCtrls = ({
|
||||
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 (
|
||||
<View style={[styles.ctrls, style]}>
|
||||
<View
|
||||
style={[
|
||||
styles.ctrl,
|
||||
big ? styles.ctrlBig : styles.ctrl,
|
||||
post.viewer?.replyDisabled ? {opacity: 0.5} : undefined,
|
||||
]}>
|
||||
<TouchableOpacity
|
||||
@@ -149,7 +161,7 @@ let PostCtrls = ({
|
||||
) : undefined}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.ctrl}>
|
||||
<View style={big ? styles.ctrlBig : styles.ctrl}>
|
||||
<RepostButton
|
||||
big={big}
|
||||
isReposted={!!post.viewer?.repost}
|
||||
@@ -158,7 +170,7 @@ let PostCtrls = ({
|
||||
onQuote={onQuote}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.ctrl}>
|
||||
<View style={big ? styles.ctrlBig : styles.ctrl}>
|
||||
<TouchableOpacity
|
||||
testID="likeBtn"
|
||||
style={[styles.btn, !big && styles.btnPad]}
|
||||
@@ -193,20 +205,34 @@ let PostCtrls = ({
|
||||
) : undefined}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{big ? undefined : (
|
||||
<View style={styles.ctrl}>
|
||||
<PostDropdownBtn
|
||||
testID="postDropdownBtn"
|
||||
postAuthor={post.author}
|
||||
postCid={post.cid}
|
||||
postUri={post.uri}
|
||||
record={record}
|
||||
richText={richText}
|
||||
showAppealLabelItem={showAppealLabelItem}
|
||||
style={styles.btnPad}
|
||||
/>
|
||||
{big && (
|
||||
<View style={styles.ctrlBig}>
|
||||
<TouchableOpacity
|
||||
testID="likeBtn"
|
||||
style={[styles.btn]}
|
||||
onPress={onShare}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`${
|
||||
post.viewer?.like ? _(msg`Unlike`) : _(msg`Like`)
|
||||
} (${post.likeCount} ${pluralize(post.likeCount || 0, 'like')})`}
|
||||
accessibilityHint=""
|
||||
hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
|
||||
<ArrowOutOfBox style={[defaultCtrlColor, styles.mt1]} width={22} />
|
||||
</TouchableOpacity>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
@@ -223,6 +249,9 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
ctrlBig: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -37,8 +37,8 @@ export const CommunityGuidelinesScreen = (_props: Props) => {
|
||||
The Community Guidelines have been moved to{' '}
|
||||
<TextLink
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/community-guidelines"
|
||||
text="blueskyweb.xyz/support/community-guidelines"
|
||||
href="https://bsky.social/about/support/community-guidelines"
|
||||
text="bsky.social/about/support/community-guidelines"
|
||||
/>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -34,8 +34,8 @@ export const CopyrightPolicyScreen = (_props: Props) => {
|
||||
The Copyright Policy has been moved to{' '}
|
||||
<TextLink
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/community-guidelines"
|
||||
text="blueskyweb.xyz/support/community-guidelines"
|
||||
href="https://bsky.social/about/support/copyright"
|
||||
text="bsky.social/about/support/copyright"
|
||||
/>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
+18
-13
@@ -7,7 +7,7 @@ import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
|
||||
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
|
||||
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
|
||||
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 {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
|
||||
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 {emitSoftReset} from '#/state/events'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
||||
|
||||
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
||||
export function HomeScreen(props: Props) {
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
|
||||
usePinnedFeedsInfos()
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
const [rawInitialFeed] = React.useState<string>(
|
||||
() => persisted.get('lastSelectedHomeFeed') ?? 'home',
|
||||
)
|
||||
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
|
||||
return (
|
||||
<HomeScreenReady
|
||||
{...props}
|
||||
preferences={preferences}
|
||||
pinnedFeedInfos={pinnedFeedInfos}
|
||||
rawInitialFeed={isDesktop ? 'home' : rawInitialFeed}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -49,11 +43,9 @@ export function HomeScreen(props: Props) {
|
||||
function HomeScreenReady({
|
||||
preferences,
|
||||
pinnedFeedInfos,
|
||||
rawInitialFeed,
|
||||
}: Props & {
|
||||
preferences: UsePreferencesQueryResponse
|
||||
pinnedFeedInfos: FeedSourceInfo[]
|
||||
rawInitialFeed: string
|
||||
}) {
|
||||
const allFeeds = React.useMemo(() => {
|
||||
const feeds: FeedDescriptor[] = []
|
||||
@@ -68,12 +60,24 @@ function HomeScreenReady({
|
||||
return feeds
|
||||
}, [pinnedFeedInfos])
|
||||
|
||||
const [rawSelectedFeed, setSelectedFeed] =
|
||||
React.useState<string>(rawInitialFeed)
|
||||
const rawSelectedFeed = useSelectedFeed()
|
||||
const setSelectedFeed = useSetSelectedFeed()
|
||||
const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed as FeedDescriptor)
|
||||
const selectedIndex = Math.max(0, maybeFoundIndex)
|
||||
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 setMinimalShellMode = useSetMinimalShellMode()
|
||||
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
|
||||
@@ -93,7 +97,7 @@ function HomeScreenReady({
|
||||
setDrawerSwipeDisabled(index > 0)
|
||||
const feed = allFeeds[index]
|
||||
setSelectedFeed(feed)
|
||||
persisted.write('lastSelectedHomeFeed', feed)
|
||||
lastPagerReportedIndexRef.current = index
|
||||
},
|
||||
[setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds],
|
||||
)
|
||||
@@ -147,6 +151,7 @@ function HomeScreenReady({
|
||||
return hasSession ? (
|
||||
<Pager
|
||||
key={allFeeds.join(',')}
|
||||
ref={pagerRef}
|
||||
testID="homeScreen"
|
||||
initialPage={selectedIndex}
|
||||
onPageSelected={onPageSelected}
|
||||
|
||||
@@ -29,6 +29,9 @@ import {listenSoftReset, emitSoftReset} from '#/state/events'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
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<
|
||||
NotificationsTabNavigatorParams,
|
||||
@@ -47,6 +50,7 @@ export function NotificationsScreen({}: Props) {
|
||||
const unreadApi = useUnreadNotificationsApi()
|
||||
const hasNew = !!unreadNotifs
|
||||
const isScreenFocused = useIsFocused()
|
||||
const {openComposer} = useComposerControls()
|
||||
|
||||
// event handlers
|
||||
// =
|
||||
@@ -156,6 +160,14 @@ export function NotificationsScreen({}: Props) {
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ export const PrivacyPolicyScreen = (_props: Props) => {
|
||||
The Privacy Policy has been moved to{' '}
|
||||
<TextLink
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/privacy-policy"
|
||||
text="blueskyweb.xyz/support/privacy-policy"
|
||||
href="https://bsky.social/about/support/privacy-policy"
|
||||
text="bsky.social/about/support/privacy-policy"
|
||||
/>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import React, {useMemo} from 'react'
|
||||
import {StyleSheet, View} from 'react-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 {useLingui} from '@lingui/react'
|
||||
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 {ProfileLists} from '../com/lists/ProfileLists'
|
||||
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 {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {EmptyState} from '../com/util/EmptyState'
|
||||
@@ -28,7 +33,7 @@ import {
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
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 {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info'
|
||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||
@@ -50,6 +55,7 @@ interface SectionRef {
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
|
||||
export function ProfileScreen({route}: Props) {
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const name =
|
||||
route.params.name === 'me' ? currentAccount?.did : route.params.name
|
||||
@@ -65,6 +71,7 @@ export function ProfileScreen({route}: Props) {
|
||||
error: profileError,
|
||||
refetch: refetchProfile,
|
||||
isLoading: isLoadingProfile,
|
||||
isPlaceholderData: isPlaceholderProfile,
|
||||
} = useProfileQuery({
|
||||
did: resolvedDid,
|
||||
})
|
||||
@@ -84,27 +91,23 @@ export function ProfileScreen({route}: Props) {
|
||||
}
|
||||
}, [profile?.viewer?.blockedBy, resolvedDid])
|
||||
|
||||
if (isLoadingDid || isLoadingProfile || !moderationOpts) {
|
||||
// Most pushes will happen here, since we will have only placeholder data
|
||||
if (isLoadingDid || isLoadingProfile) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<ProfileHeader
|
||||
profile={null}
|
||||
moderation={null}
|
||||
isProfilePreview={true}
|
||||
/>
|
||||
<ProfileHeaderLoading />
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
if (resolveError || profileError) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title="Oops!"
|
||||
message={cleanError(resolveError || profileError)}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
/>
|
||||
</CenteredView>
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title={profileError ? _(msg`Not Found`) : _(msg`Oops!`)}
|
||||
message={cleanError(resolveError || profileError)}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
showHeader
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (profile && moderationOpts) {
|
||||
@@ -112,31 +115,33 @@ export function ProfileScreen({route}: Props) {
|
||||
<ProfileScreenLoaded
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
isPlaceholderProfile={isPlaceholderProfile}
|
||||
hideBackButton={!!route.params.hideBackButton}
|
||||
/>
|
||||
)
|
||||
}
|
||||
// should never happen
|
||||
return (
|
||||
<CenteredView>
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title="Oops!"
|
||||
message="Something went wrong and we're not sure what."
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
/>
|
||||
</CenteredView>
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title="Oops!"
|
||||
message="Something went wrong and we're not sure what."
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
showHeader
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileScreenLoaded({
|
||||
profile: profileUnshadowed,
|
||||
isPlaceholderProfile,
|
||||
moderationOpts,
|
||||
hideBackButton,
|
||||
}: {
|
||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||
moderationOpts: ModerationOpts
|
||||
hideBackButton: boolean
|
||||
isPlaceholderProfile: boolean
|
||||
}) {
|
||||
const profile = useProfileShadow(profileUnshadowed)
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
@@ -156,6 +161,10 @@ function ProfileScreenLoaded({
|
||||
|
||||
useSetTitle(combinedDisplayName(profile))
|
||||
|
||||
const description = profile.description ?? ''
|
||||
const hasDescription = description !== ''
|
||||
const [descriptionRT, isResolvingDescriptionRT] = useRichText(description)
|
||||
const showPlaceholder = isPlaceholderProfile || isResolvingDescriptionRT
|
||||
const moderation = useMemo(
|
||||
() => moderateProfile(profile, moderationOpts),
|
||||
[profile, moderationOpts],
|
||||
@@ -269,11 +278,20 @@ function ProfileScreenLoaded({
|
||||
return (
|
||||
<ProfileHeader
|
||||
profile={profile}
|
||||
moderation={moderation}
|
||||
descriptionRT={hasDescription ? descriptionRT : null}
|
||||
moderationOpts={moderationOpts}
|
||||
hideBackButton={hideBackButton}
|
||||
isPlaceholderProfile={showPlaceholder}
|
||||
/>
|
||||
)
|
||||
}, [profile, moderation, hideBackButton])
|
||||
}, [
|
||||
profile,
|
||||
descriptionRT,
|
||||
hasDescription,
|
||||
moderationOpts,
|
||||
hideBackButton,
|
||||
showPlaceholder,
|
||||
])
|
||||
|
||||
return (
|
||||
<ScreenHider
|
||||
@@ -283,7 +301,7 @@ function ProfileScreenLoaded({
|
||||
moderation={moderation.account}>
|
||||
<PagerWithHeader
|
||||
testID="profilePager"
|
||||
isHeaderReady={true}
|
||||
isHeaderReady={!showPlaceholder}
|
||||
items={sectionTitles}
|
||||
onPageSelected={onPageSelected}
|
||||
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({
|
||||
container: {
|
||||
flexDirection: 'column',
|
||||
|
||||
@@ -863,13 +863,13 @@ export function SettingsScreen({}: Props) {
|
||||
<TextLink
|
||||
type="md"
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/tos"
|
||||
href="https://bsky.social/about/support/tos"
|
||||
text={_(msg`Terms of Service`)}
|
||||
/>
|
||||
<TextLink
|
||||
type="md"
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/privacy-policy"
|
||||
href="https://bsky.social/about/support/privacy-policy"
|
||||
text={_(msg`Privacy Policy`)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -14,19 +14,19 @@ export function Links() {
|
||||
|
||||
<View style={[a.gap_md, a.align_start]}>
|
||||
<InlineLink
|
||||
to="https://blueskyweb.xyz"
|
||||
to="https://bsky.social"
|
||||
warnOnMismatchingTextChild
|
||||
style={[a.text_md]}>
|
||||
External
|
||||
</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>
|
||||
</InlineLink>
|
||||
<InlineLink
|
||||
to="https://blueskyweb.xyz"
|
||||
to="https://bsky.social"
|
||||
warnOnMismatchingTextChild
|
||||
style={[a.text_lg]}>
|
||||
https://blueskyweb.xyz
|
||||
https://bsky.social
|
||||
</InlineLink>
|
||||
<InlineLink
|
||||
to="https://bsky.app/profile/bsky.app"
|
||||
|
||||
@@ -14,22 +14,22 @@ export function Theming() {
|
||||
|
||||
<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]} />
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_600, a.px_md]}>
|
||||
theme.atoms.text_contrast_600
|
||||
<View style={[a.flex_1, t.atoms.border_contrast_high, a.border_t]} />
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_high, a.px_md]}>
|
||||
theme.atoms.text_contrast_high
|
||||
</Text>
|
||||
|
||||
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_500, a.px_md]}>
|
||||
theme.atoms.text_contrast_500
|
||||
<View style={[a.flex_1, t.atoms.border_contrast_medium, a.border_t]} />
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_medium, a.px_md]}>
|
||||
theme.atoms.text_contrast_medium
|
||||
</Text>
|
||||
|
||||
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_400, a.px_md]}>
|
||||
theme.atoms.text_contrast_400
|
||||
<View style={[a.flex_1, t.atoms.border_contrast_low, a.border_t]} />
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_low, a.px_md]}>
|
||||
theme.atoms.text_contrast_low
|
||||
</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={[t.atoms.bg, a.justify_center, a.p_md]}>
|
||||
|
||||
@@ -2,20 +2,12 @@ import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
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'
|
||||
|
||||
export function Typography() {
|
||||
return (
|
||||
<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_4xl]}>atoms.text_4xl</Text>
|
||||
<Text style={[a.text_3xl]}>atoms.text_3xl</Text>
|
||||
@@ -29,11 +21,11 @@ export function Typography() {
|
||||
|
||||
<RichText
|
||||
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
|
||||
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]}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -33,8 +33,8 @@ export const TermsOfServiceScreen = (_props: Props) => {
|
||||
<Trans>The Terms of Service have been moved to</Trans>{' '}
|
||||
<TextLink
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/tos"
|
||||
text="blueskyweb.xyz/support/tos"
|
||||
href="https://bsky.social/about/support/tos"
|
||||
text="bsky.social/about/support/tos"
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -255,13 +255,13 @@ let DrawerContent = ({}: {}): React.ReactNode => {
|
||||
<TextLink
|
||||
type="md"
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/tos"
|
||||
href="https://bsky.social/about/support/tos"
|
||||
text={_(msg`Terms of Service`)}
|
||||
/>
|
||||
<TextLink
|
||||
type="md"
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/privacy-policy"
|
||||
href="https://bsky.social/about/support/privacy-policy"
|
||||
text={_(msg`Privacy Policy`)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import React from 'react'
|
||||
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 {TextLink} from 'view/com/util/Link'
|
||||
import {getCurrentRoute} from 'lib/routes/helpers'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
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() {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {feeds} = usePinnedFeedsInfos()
|
||||
|
||||
const {feeds: pinnedFeedInfos} = usePinnedFeedsInfos()
|
||||
const selectedFeed = useSelectedFeed()
|
||||
const setSelectedFeed = useSetSelectedFeed()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const route = useNavigationState(state => {
|
||||
if (!state) {
|
||||
return {name: 'Home'}
|
||||
@@ -22,30 +28,34 @@ export function DesktopFeeds() {
|
||||
|
||||
return (
|
||||
<View style={[styles.container, pal.view]}>
|
||||
<FeedItem href="/" title="Following" current={route.name === 'Home'} />
|
||||
{feeds
|
||||
.filter(f => f.displayName !== 'Following')
|
||||
.map(feed => {
|
||||
try {
|
||||
const params = route.params as Record<string, string>
|
||||
const routeName =
|
||||
feed.type === 'feed' ? 'ProfileFeed' : 'ProfileList'
|
||||
return (
|
||||
<FeedItem
|
||||
key={feed.uri}
|
||||
href={feed.route.href}
|
||||
title={feed.displayName}
|
||||
current={
|
||||
route.name === routeName &&
|
||||
params.name === feed.route.params.name &&
|
||||
params.rkey === feed.route.params.rkey
|
||||
}
|
||||
/>
|
||||
)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
{pinnedFeedInfos.map(feedInfo => {
|
||||
const uri = feedInfo.uri
|
||||
let feed: FeedDescriptor
|
||||
if (!uri) {
|
||||
feed = 'home'
|
||||
} else if (uri.includes('app.bsky.feed.generator')) {
|
||||
feed = `feedgen|${uri}`
|
||||
} else if (uri.includes('app.bsky.graph.list')) {
|
||||
feed = `list|${uri}`
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<FeedItem
|
||||
key={feed}
|
||||
href={'/?' + new URLSearchParams([['feed', feed]])}
|
||||
title={feedInfo.displayName}
|
||||
current={route.name === 'Home' && feed === selectedFeed}
|
||||
onPress={() => {
|
||||
setSelectedFeed(feed)
|
||||
navigation.navigate('Home')
|
||||
if (feed === selectedFeed) {
|
||||
emitSoftReset()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<View style={{paddingTop: 8, paddingBottom: 6}}>
|
||||
<TextLink
|
||||
type="lg"
|
||||
@@ -62,10 +72,12 @@ function FeedItem({
|
||||
title,
|
||||
href,
|
||||
current,
|
||||
onPress,
|
||||
}: {
|
||||
title: string
|
||||
href: string
|
||||
current: boolean
|
||||
onPress: () => void
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
return (
|
||||
@@ -74,6 +86,7 @@ function FeedItem({
|
||||
type="xl"
|
||||
href={href}
|
||||
text={title}
|
||||
onPress={onPress}
|
||||
style={[
|
||||
current ? pal.text : pal.textLight,
|
||||
{letterSpacing: 0.15, fontWeight: current ? '500' : 'normal'},
|
||||
|
||||
@@ -334,7 +334,7 @@ export function DesktopLeftNav() {
|
||||
}
|
||||
iconFilled={
|
||||
<HashtagIcon
|
||||
strokeWidth={2.5}
|
||||
strokeWidth={4}
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
size={isDesktop ? 24 : 28}
|
||||
/>
|
||||
|
||||
@@ -80,7 +80,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
<TextLink
|
||||
type="md"
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/privacy-policy"
|
||||
href="https://bsky.social/about/support/privacy-policy"
|
||||
text={_(msg`Privacy`)}
|
||||
/>
|
||||
<Text type="md" style={pal.textLight}>
|
||||
@@ -89,7 +89,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
<TextLink
|
||||
type="md"
|
||||
style={pal.link}
|
||||
href="https://blueskyweb.xyz/support/tos"
|
||||
href="https://bsky.social/about/support/tos"
|
||||
text={_(msg`Terms`)}
|
||||
/>
|
||||
<Text type="md" style={pal.textLight}>
|
||||
|
||||
+4
-5
@@ -1,8 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="%LANG_ISO_CODE%">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta charset="utf-8">
|
||||
<!--
|
||||
This viewport works for phones with notches.
|
||||
It's optimized for gestures by disabling global zoom.
|
||||
@@ -234,8 +233,8 @@
|
||||
width: 80%;
|
||||
"
|
||||
>
|
||||
<p>Oh no! It looks like JavaScript is not enabled in your browser.</p>
|
||||
<p style="margin: 20px 0;">
|
||||
<p lang="en">Oh no! It looks like JavaScript is not enabled in your browser.</p>
|
||||
<p lang="en" style="margin: 20px 0;">
|
||||
<button
|
||||
type="submit"
|
||||
style="
|
||||
|
||||
Reference in New Issue
Block a user