Merge remote-tracking branch 'origin/main' into samuel/alf-login
commit
4794ab6b9a
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
name: Bundle and Deploy EAS Update
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
runtimeVersion:
|
||||
type: string
|
||||
description: Runtime version (in x.x.x format) that this update is for
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
bundleDeploy:
|
||||
name: Bundle and Deploy EAS Update
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 🧐 Validate version
|
||||
run: |
|
||||
[[ "${{ github.event.inputs.runtimeVersion }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1
|
||||
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🔧 Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: yarn
|
||||
|
||||
- name: ⚙️ Install Dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: 🪛 Install jq
|
||||
uses: dcarbone/install-jq-action@v2
|
||||
|
||||
- name: ⛏️ Setup Expo
|
||||
run: yarn global add eas-cli-local-build-plugin
|
||||
|
||||
- name: 🔤 Compile Translations
|
||||
run: yarn intl:build
|
||||
|
||||
- name: ✏️ Write environment variables
|
||||
run: |
|
||||
export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}'
|
||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
||||
echo "$json" > google-services.json
|
||||
|
||||
- name: 🏗️ Create Bundle
|
||||
run: yarn export
|
||||
|
||||
- name: 📦 Package Bundle and 🚀 Deploy
|
||||
run: yarn make-deploy-bundle
|
||||
env:
|
||||
DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }}
|
||||
RUNTIME_VERSION: ${{ github.event.inputs.runtimeVersion }}
|
|
@ -4,6 +4,7 @@ import {
|
|||
linkRequiresWarning,
|
||||
isPossiblyAUrl,
|
||||
splitApexDomain,
|
||||
isTrustedUrl,
|
||||
} from '../../../src/lib/strings/url-helpers'
|
||||
|
||||
describe('linkRequiresWarning', () => {
|
||||
|
@ -74,6 +75,10 @@ describe('linkRequiresWarning', () => {
|
|||
// bad uri inputs, default to true
|
||||
['', '', true],
|
||||
['example.com', 'example.com', true],
|
||||
['/profile', 'Username', false],
|
||||
['#', 'Show More', false],
|
||||
['https://docs.bsky.app', 'https://docs.bsky.app', false],
|
||||
['https://bsky.app/compose/intent?text=test', 'Compose a post', false],
|
||||
]
|
||||
|
||||
it.each(cases)(
|
||||
|
@ -139,3 +144,36 @@ describe('splitApexDomain', () => {
|
|||
},
|
||||
)
|
||||
})
|
||||
|
||||
describe('isTrustedUrl', () => {
|
||||
const cases = [
|
||||
['#', true],
|
||||
['#profile', true],
|
||||
['/', true],
|
||||
['/profile', true],
|
||||
['/profile/', true],
|
||||
['/profile/bob.test', true],
|
||||
['https://bsky.app', true],
|
||||
['https://bsky.app/', true],
|
||||
['https://bsky.app/profile/bob.test', true],
|
||||
['https://www.bsky.app', true],
|
||||
['https://www.bsky.app/', true],
|
||||
['https://docs.bsky.app', true],
|
||||
['https://bsky.social', true],
|
||||
['https://bsky.social/blog', true],
|
||||
['https://blueskyweb.xyz', true],
|
||||
['https://blueskyweb.zendesk.com', true],
|
||||
['http://bsky.app', true],
|
||||
['http://bsky.social', true],
|
||||
['http://blueskyweb.xyz', true],
|
||||
['http://blueskyweb.zendesk.com', true],
|
||||
['https://google.com', false],
|
||||
['https://docs.google.com', false],
|
||||
['https://google.com/#', false],
|
||||
]
|
||||
|
||||
it.each(cases)('given input uri %p, returns %p', (str, expected) => {
|
||||
const output = isTrustedUrl(str)
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC0TCCAbmgAwIBAgIJcMN2yt5KNDqTMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
|
||||
BAMTB0JsdWVza3kwHhcNMjQwMzE0MDA1OTU4WhcNMzQwMzE0MDA1OTU4WjASMRAw
|
||||
DgYDVQQDEwdCbHVlc2t5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||
izSAWEc3wRoa3eTBEh/kE9pH0d6jhEGw9GrYfei60MHT1pSq2cTdyUM1yUZchAeW
|
||||
gFFtqFxX0pfIZQyMlIZbjkaOxOqzWhB0aCsxngnhbSahFwRxkVwTAuonhqIpaLBL
|
||||
hrCCCQ2IfZUpy8QeasqlTlmvmijuCC34fXxJlxNcj8SqzIZi+civ7U5PMPfIMMnD
|
||||
tCDIBy1vxMk57m25X2ikcWUFW64qNVLkFAL36xEnmFTL4Ivqpz23gUcUIe1zbesY
|
||||
jAgDtlwnAE7mU3oagCUDcSuOveT4POhT35Xp3Y/07I68kmXtrPxwd5k0L0zbisEm
|
||||
poKZ87E2X29BitihicMpBwIDAQABoyowKDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0l
|
||||
AQH/BAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAED1gdMF0yr8Gy87
|
||||
RgyaeVpPySwSsO0selmXXrcmOWgiPA05lubyhFEa4P5kdzBEByG2MT+pJkjGYpvK
|
||||
XRnqXM5VvdS2RhYYFH0cFOIUqBKwCnzViCMuGQeoGUx4oPcKFS0PQ1WjW2d4pS75
|
||||
51GBfB6LOepsCHUG0A9XEk7EAyUWc4M2ITCJsTtJh8CVn2pTks2q14ETDs86YQv4
|
||||
peDaJv8nhIe8oQkeGn2o/P/ctkwJg/uBydQUsWgjjGTQZTilVjGTW1mwDr9FucAE
|
||||
d5gKIk4rtR/3Zd/NDdqp8PrkoWeVM7Hwr789/mpUOeqa/j7YNkDYQh7x+M/odd1D
|
||||
KY0bQEQ=
|
||||
-----END CERTIFICATE-----
|
|
@ -41,10 +41,12 @@
|
|||
"intl:extract": "lingui extract",
|
||||
"intl:compile": "lingui compile",
|
||||
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android",
|
||||
"update-extensions": "scripts/updateExtensions.sh"
|
||||
"update-extensions": "bash scripts/updateExtensions.sh",
|
||||
"export": "npx expo export",
|
||||
"make-deploy-bundle": "bash scripts/bundleUpdate.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.12.0",
|
||||
"@atproto/api": "^0.12.1",
|
||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
|
|
|
@ -1,8 +1,56 @@
|
|||
diff --git a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
|
||||
index 3f50f8c..ee47fa1 100644
|
||||
--- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
|
||||
+++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
|
||||
@@ -33,7 +33,9 @@ import kotlin.coroutines.resumeWithException
|
||||
// TODO(@bbarthec): rename to ExpoImagePicker
|
||||
private const val moduleName = "ExponentImagePicker"
|
||||
|
||||
+
|
||||
class ImagePickerModule : Module() {
|
||||
+ private var isPickerOpen = false
|
||||
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name(moduleName)
|
||||
@@ -129,6 +131,11 @@ class ImagePickerModule : Module() {
|
||||
options: ImagePickerOptions
|
||||
): Any {
|
||||
return try {
|
||||
+ if(isPickerOpen) {
|
||||
+ return ImagePickerResponse(canceled = true)
|
||||
+ }
|
||||
+
|
||||
+ isPickerOpen = true
|
||||
var result = launchPicker(pickerLauncher)
|
||||
if (
|
||||
!options.allowsMultipleSelection &&
|
||||
@@ -143,6 +150,8 @@ class ImagePickerModule : Module() {
|
||||
mediaHandler.readExtras(result.data, options)
|
||||
} catch (cause: OperationCanceledException) {
|
||||
return ImagePickerResponse(canceled = true)
|
||||
+ } finally {
|
||||
+ isPickerOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt
|
||||
index ff15c91..41aaf12 100644
|
||||
index ff15c91..9763012 100644
|
||||
--- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt
|
||||
+++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt
|
||||
@@ -26,51 +26,26 @@ import java.io.Serializable
|
||||
@@ -5,12 +5,7 @@ import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
-import androidx.activity.result.PickVisualMediaRequest
|
||||
-import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
|
||||
-import androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia
|
||||
import expo.modules.imagepicker.ImagePickerOptions
|
||||
-import expo.modules.imagepicker.MediaTypes
|
||||
-import expo.modules.imagepicker.UNLIMITED_SELECTION
|
||||
import expo.modules.imagepicker.getAllDataUris
|
||||
import expo.modules.imagepicker.toMediaType
|
||||
import expo.modules.kotlin.activityresult.AppContextActivityResultContract
|
||||
@@ -26,51 +21,26 @@ import java.io.Serializable
|
||||
* @see [androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents]
|
||||
*/
|
||||
internal class ImageLibraryContract(
|
||||
|
@ -12,7 +60,7 @@ index ff15c91..41aaf12 100644
|
|||
private val contentResolver: ContentResolver
|
||||
get() = appContextProvider.appContext.reactContext?.contentResolver
|
||||
?: throw Exceptions.ReactContextLost()
|
||||
|
||||
|
||||
override fun createIntent(context: Context, input: ImageLibraryContractOptions): Intent {
|
||||
- val request = PickVisualMediaRequest.Builder()
|
||||
- .setMediaType(
|
||||
|
@ -34,7 +82,7 @@ index ff15c91..41aaf12 100644
|
|||
+ val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
+ .addCategory(Intent.CATEGORY_OPENABLE)
|
||||
+ .setType("image/*")
|
||||
|
||||
|
||||
if (input.options.allowsMultipleSelection) {
|
||||
- val selectionLimit = input.options.selectionLimit
|
||||
-
|
||||
|
@ -45,7 +93,7 @@ index ff15c91..41aaf12 100644
|
|||
+ if(input.options.selectionLimit == 1) {
|
||||
+ return intent
|
||||
}
|
||||
|
||||
|
||||
- if (selectionLimit > 1) {
|
||||
- return PickMultipleVisualMedia(selectionLimit).createIntent(context, request)
|
||||
- }
|
||||
|
@ -56,9 +104,9 @@ index ff15c91..41aaf12 100644
|
|||
- }
|
||||
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
}
|
||||
|
||||
|
||||
- return PickVisualMedia().createIntent(context, request)
|
||||
+ return intent
|
||||
}
|
||||
|
||||
|
||||
override fun parseResult(input: ImageLibraryContractOptions, resultCode: Int, intent: Intent?) =
|
||||
|
|
|
@ -1,92 +1,19 @@
|
|||
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
|
||||
index 9dca6a5..090bda5 100644
|
||||
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
|
||||
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
|
||||
@@ -266,11 +266,10 @@ - (void)textViewDidChange:(__unused UITextView *)textView
|
||||
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
index b09e653..d290dab 100644
|
||||
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
@@ -198,6 +198,14 @@ - (void)refreshControlValueChanged
|
||||
[self setCurrentRefreshingState:super.refreshing];
|
||||
_refreshingProgrammatically = NO;
|
||||
|
||||
- (void)textViewDidChangeSelection:(__unused UITextView *)textView
|
||||
{
|
||||
- if (_lastStringStateWasUpdatedWith && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
+ if (![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
[self textViewDidChange:_backedTextInputView];
|
||||
_ignoreNextTextInputCall = YES;
|
||||
}
|
||||
- _lastStringStateWasUpdatedWith = _backedTextInputView.attributedText;
|
||||
[self textViewProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm
|
||||
index 1f06b79..ab458f3 100644
|
||||
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm
|
||||
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm
|
||||
@@ -87,7 +87,7 @@ - (void)invalidateContentSize
|
||||
return;
|
||||
}
|
||||
|
||||
- CGSize maximumSize = self.layoutMetrics.frame.size;
|
||||
+ CGSize maximumSize = self.layoutMetrics.contentFrame.size;
|
||||
|
||||
if (_maximumNumberOfLines == 1) {
|
||||
maximumSize.width = CGFLOAT_MAX;
|
||||
@@ -158,6 +158,8 @@ - (void)uiManagerWillPerformMounting
|
||||
[attributedText insertAttributedString:propertyAttributedText atIndex:0];
|
||||
}
|
||||
|
||||
+ [self postprocessAttributedText:attributedText];
|
||||
+
|
||||
NSAttributedString *newAttributedText;
|
||||
if (![_previousAttributedText isEqualToAttributedString:attributedText]) {
|
||||
// We have to follow `set prop` pattern:
|
||||
@@ -191,6 +193,52 @@ - (void)uiManagerWillPerformMounting
|
||||
}];
|
||||
}
|
||||
|
||||
+- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText
|
||||
+{
|
||||
+ __block CGFloat maximumLineHeight = 0;
|
||||
+
|
||||
+ [attributedText enumerateAttribute:NSParagraphStyleAttributeName
|
||||
+ inRange:NSMakeRange(0, attributedText.length)
|
||||
+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
||||
+ usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
|
||||
+ if (!paragraphStyle) {
|
||||
+ return;
|
||||
+ if (@available(iOS 17.4, *)) {
|
||||
+ if (_currentRefreshingState) {
|
||||
+ UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
|
||||
+ [feedbackGenerator prepare];
|
||||
+ [feedbackGenerator impactOccurred];
|
||||
+ }
|
||||
+
|
||||
+ maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
|
||||
+ }];
|
||||
+
|
||||
+ if (maximumLineHeight == 0) {
|
||||
+ // `lineHeight` was not specified, nothing to do.
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ __block CGFloat maximumFontLineHeight = 0;
|
||||
+
|
||||
+ [attributedText enumerateAttribute:NSFontAttributeName
|
||||
+ inRange:NSMakeRange(0, attributedText.length)
|
||||
+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
||||
+ usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) {
|
||||
+ if (!font) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (maximumFontLineHeight <= font.lineHeight) {
|
||||
+ maximumFontLineHeight = font.lineHeight;
|
||||
+ }
|
||||
+ }];
|
||||
+
|
||||
+ if (maximumLineHeight < maximumFontLineHeight) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ CGFloat baseLineOffset = maximumLineHeight / 2.0 - maximumFontLineHeight / 2.0;
|
||||
+
|
||||
+ [attributedText addAttribute:NSBaselineOffsetAttributeName
|
||||
+ value:@(baseLineOffset)
|
||||
+ range:NSMakeRange(0, attributedText.length)];
|
||||
+}
|
||||
+
|
||||
#pragma mark -
|
||||
|
||||
- (NSAttributedString *)measurableAttributedText
|
||||
if (_onRefresh) {
|
||||
_onRefresh(nil);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
# TextInput Patch
|
||||
# RefreshControl Patch
|
||||
|
||||
Patching `RCTBaseTextShadowInput.mm` from https://github.com/facebook/react-native/pull/38359. This fixes some text
|
||||
getting cut off inside the composer. This was merged in December, so we should be able to remove this patch when RN
|
||||
ships the next release.
|
||||
Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh when using iOS 17.4 or higher. Since
|
||||
17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update
|
||||
in the RN repo: https://github.com/facebook/react-native/issues/43388
|
|
@ -0,0 +1,104 @@
|
|||
const crypto = require('crypto')
|
||||
const fs = require('fs')
|
||||
const fsp = fs.promises
|
||||
const path = require('path')
|
||||
|
||||
const DIST_DIR = './dist'
|
||||
const BUNDLES_DIR = '/_expo/static/js'
|
||||
const IOS_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/ios')
|
||||
const ANDROID_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/android')
|
||||
const METADATA_PATH = path.join(DIST_DIR, '/metadata.json')
|
||||
const DEST_DIR = './bundleTempDir'
|
||||
|
||||
// Weird, don't feel like figuring out _why_ it wants this
|
||||
const METADATA = require(`../${METADATA_PATH}`)
|
||||
const IOS_METADATA_ASSETS = METADATA.fileMetadata.ios.assets
|
||||
const ANDROID_METADATA_ASSETS = METADATA.fileMetadata.android.assets
|
||||
|
||||
const getMd5 = async path => {
|
||||
return new Promise(res => {
|
||||
const hash = crypto.createHash('md5')
|
||||
const rStream = fs.createReadStream(path)
|
||||
rStream.on('data', data => {
|
||||
hash.update(data)
|
||||
})
|
||||
rStream.on('end', () => {
|
||||
res(hash.digest('hex'))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const moveFiles = async () => {
|
||||
console.log('Making directory...')
|
||||
await fsp.mkdir(DEST_DIR)
|
||||
await fsp.mkdir(path.join(DEST_DIR, '/assets'))
|
||||
|
||||
console.log('Getting ios md5...')
|
||||
const iosCurrPath = path.join(
|
||||
IOS_BUNDLE_DIR,
|
||||
(await fsp.readdir(IOS_BUNDLE_DIR))[0],
|
||||
)
|
||||
const iosMd5 = await getMd5(iosCurrPath)
|
||||
const iosNewPath = `bundles/${iosMd5}.bundle`
|
||||
|
||||
console.log('Copying ios bundle...')
|
||||
await fsp.cp(iosCurrPath, path.join(DEST_DIR, iosNewPath))
|
||||
|
||||
console.log('Getting android md5...')
|
||||
const androidCurrPath = path.join(
|
||||
ANDROID_BUNDLE_DIR,
|
||||
(await fsp.readdir(ANDROID_BUNDLE_DIR))[0],
|
||||
)
|
||||
const androidMd5 = await getMd5(androidCurrPath)
|
||||
const androidNewPath = `bundles/${androidMd5}.bundle`
|
||||
|
||||
console.log('Copying android bundle...')
|
||||
await fsp.cp(androidCurrPath, path.join(DEST_DIR, androidNewPath))
|
||||
|
||||
const iosAssets = []
|
||||
const androidAssets = []
|
||||
|
||||
console.log('Getting ios asset md5s and moving them...')
|
||||
for (const asset of IOS_METADATA_ASSETS) {
|
||||
const currPath = path.join(DIST_DIR, asset.path)
|
||||
const md5 = await getMd5(currPath)
|
||||
const withExtPath = `assets/${md5}.${asset.ext}`
|
||||
iosAssets.push(withExtPath)
|
||||
await fsp.cp(currPath, path.join(DEST_DIR, withExtPath))
|
||||
}
|
||||
|
||||
console.log('Getting android asset md5s and moving them...')
|
||||
for (const asset of ANDROID_METADATA_ASSETS) {
|
||||
const currPath = path.join(DIST_DIR, asset.path)
|
||||
const md5 = await getMd5(currPath)
|
||||
const withExtPath = `assets/${md5}.${asset.ext}`
|
||||
androidAssets.push(withExtPath)
|
||||
await fsp.cp(currPath, path.join(DEST_DIR, withExtPath))
|
||||
}
|
||||
|
||||
const result = {
|
||||
version: 0,
|
||||
bundler: 'metro',
|
||||
fileMetadata: {
|
||||
ios: {
|
||||
bundle: iosNewPath,
|
||||
assets: iosAssets,
|
||||
},
|
||||
android: {
|
||||
bundle: androidNewPath,
|
||||
assets: androidAssets,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
console.log('Writing metadata...')
|
||||
await fsp.writeFile(
|
||||
path.join(DEST_DIR, 'metadata.json'),
|
||||
JSON.stringify(result),
|
||||
)
|
||||
|
||||
console.log('Finished!')
|
||||
console.log('Metadata:', result)
|
||||
}
|
||||
|
||||
moveFiles()
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
rm -rf bundleTempDir
|
||||
rm -rf bundle.tar.gz
|
||||
|
||||
echo "Creating tarball..."
|
||||
node scripts/bundleUpdate.js
|
||||
|
||||
cd bundleTempDir || exit
|
||||
|
||||
BUNDLE_VERSION=$(date +%s)
|
||||
DEPLOYMENT_URL="https://updates.bsky.app/v1/upload?runtime-version=$RUNTIME_VERSION&bundle-version=$BUNDLE_VERSION"
|
||||
|
||||
tar czvf bundle.tar.gz ./*
|
||||
|
||||
echo "Deploying to $DEPLOYMENT_URL..."
|
||||
|
||||
curl -o - --form "bundle=@./bundle.tar.gz" --user "bsky:$DENIS_API_KEY" --basic "$DEPLOYMENT_URL"
|
||||
|
||||
cd ..
|
||||
|
||||
rm -rf bundleTempDir
|
||||
rm -rf bundle.tar.gz
|
|
@ -16,6 +16,7 @@ type BreakpointName = keyof typeof breakpoints
|
|||
const breakpoints: {
|
||||
[key: string]: number
|
||||
} = {
|
||||
gtPhone: 500,
|
||||
gtMobile: 800,
|
||||
gtTablet: 1300,
|
||||
}
|
||||
|
@ -26,6 +27,7 @@ function getActiveBreakpoints({width}: {width: number}) {
|
|||
|
||||
return {
|
||||
active: active[active.length - 1],
|
||||
gtPhone: active.includes('gtPhone'),
|
||||
gtMobile: active.includes('gtMobile'),
|
||||
gtTablet: active.includes('gtTablet'),
|
||||
}
|
||||
|
@ -39,6 +41,7 @@ export const Context = React.createContext<{
|
|||
theme: themes.Theme
|
||||
breakpoints: {
|
||||
active: BreakpointName | undefined
|
||||
gtPhone: boolean
|
||||
gtMobile: boolean
|
||||
gtTablet: boolean
|
||||
}
|
||||
|
@ -47,6 +50,7 @@ export const Context = React.createContext<{
|
|||
theme: themes.light,
|
||||
breakpoints: {
|
||||
active: undefined,
|
||||
gtPhone: false,
|
||||
gtMobile: false,
|
||||
gtTablet: false,
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import type {AccessibilityProps} from 'react-native'
|
||||
import type {AccessibilityProps, GestureResponderEvent} from 'react-native'
|
||||
import {BottomSheetProps} from '@gorhom/bottom-sheet'
|
||||
|
||||
import {ViewStyleProp} from '#/alf'
|
||||
|
@ -10,9 +10,15 @@ type A11yProps = Required<AccessibilityProps>
|
|||
* Mutated by useImperativeHandle to provide a public API for controlling the
|
||||
* dialog. The methods here will actually become the handlers defined within
|
||||
* the `Dialog.Outer` component.
|
||||
*
|
||||
* `Partial<GestureResponderEvent>` here allows us to add this directly to the
|
||||
* `onPress` prop of a button, for example. If this type was not added, we
|
||||
* would need to create a function to wrap `.open()` with.
|
||||
*/
|
||||
export type DialogControlRefProps = {
|
||||
open: (options?: DialogControlOpenOptions) => void
|
||||
open: (
|
||||
options?: DialogControlOpenOptions & Partial<GestureResponderEvent>,
|
||||
) => void
|
||||
close: (callback?: () => void) => void
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import React from 'react'
|
||||
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {View} from 'react-native'
|
||||
import {Button} from '#/components/Button'
|
||||
import {useNavigation} from '@react-navigation/core'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {StackActions} from '@react-navigation/native'
|
||||
import {router} from '#/routes'
|
||||
|
||||
export function Error({
|
||||
title,
|
||||
message,
|
||||
onRetry,
|
||||
}: {
|
||||
title?: string
|
||||
message?: string
|
||||
onRetry?: () => unknown
|
||||
}) {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const canGoBack = navigation.canGoBack()
|
||||
const onGoBack = React.useCallback(() => {
|
||||
if (canGoBack) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('HomeTab')
|
||||
|
||||
// Checking the state for routes ensures that web doesn't encounter errors while going back
|
||||
if (navigation.getState()?.routes) {
|
||||
navigation.dispatch(StackActions.push(...router.matchPath('/')))
|
||||
} else {
|
||||
navigation.navigate('HomeTab')
|
||||
navigation.dispatch(StackActions.popToTop())
|
||||
}
|
||||
}
|
||||
}, [navigation, canGoBack])
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.align_center,
|
||||
!gtMobile ? a.justify_between : a.gap_5xl,
|
||||
t.atoms.border_contrast_low,
|
||||
{paddingTop: 175, paddingBottom: 110},
|
||||
]}
|
||||
sideBorders>
|
||||
<View style={[a.w_full, a.align_center, a.gap_lg]}>
|
||||
<Text style={[a.font_bold, a.text_3xl]}>{title}</Text>
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.text_center,
|
||||
t.atoms.text_contrast_high,
|
||||
{lineHeight: 1.4},
|
||||
gtMobile && {width: 450},
|
||||
]}>
|
||||
{message}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[a.gap_md, gtMobile ? {width: 350} : [a.w_full, a.px_lg]]}>
|
||||
{onRetry && (
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
label="Click here"
|
||||
onPress={onRetry}
|
||||
size="large"
|
||||
style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="solid"
|
||||
color={onRetry ? 'secondary' : 'primary'}
|
||||
label="Click here"
|
||||
onPress={onGoBack}
|
||||
size="large"
|
||||
style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}>
|
||||
Go Back
|
||||
</Button>
|
||||
</View>
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
|
@ -104,7 +104,7 @@ export function Default({
|
|||
}: LabelingServiceProps & ViewStyleProp) {
|
||||
return (
|
||||
<Outer style={style}>
|
||||
<Avatar />
|
||||
<Avatar avatar={labeler.creator.avatar} />
|
||||
<Content>
|
||||
<Title
|
||||
value={getLabelingServiceTitle({
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
import React from 'react'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {View} from 'react-native'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {Button} from '#/components/Button'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {StackActions} from '@react-navigation/native'
|
||||
import {router} from '#/routes'
|
||||
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
|
||||
import {Error} from '#/components/Error'
|
||||
|
||||
export function ListFooter({
|
||||
isFetching,
|
||||
isError,
|
||||
error,
|
||||
onRetry,
|
||||
height,
|
||||
}: {
|
||||
isFetching: boolean
|
||||
isError: boolean
|
||||
isFetching?: boolean
|
||||
isError?: boolean
|
||||
error?: string
|
||||
onRetry?: () => Promise<unknown>
|
||||
height?: number
|
||||
}) {
|
||||
const t = useTheme()
|
||||
|
||||
|
@ -29,11 +31,10 @@ export function ListFooter({
|
|||
style={[
|
||||
a.w_full,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.border_t,
|
||||
a.pb_lg,
|
||||
t.atoms.border_contrast_low,
|
||||
{height: 180},
|
||||
{height: height ?? 180, paddingTop: 30},
|
||||
]}>
|
||||
{isFetching ? (
|
||||
<Loader size="xl" />
|
||||
|
@ -53,11 +54,12 @@ function ListFooterMaybeError({
|
|||
error,
|
||||
onRetry,
|
||||
}: {
|
||||
isError: boolean
|
||||
isError?: boolean
|
||||
error?: string
|
||||
onRetry?: () => Promise<unknown>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
if (!isError) return null
|
||||
|
||||
|
@ -83,7 +85,7 @@ function ListFooterMaybeError({
|
|||
</Text>
|
||||
<Button
|
||||
variant="gradient"
|
||||
label="Press to retry"
|
||||
label={_(msg`Press to retry`)}
|
||||
style={[
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
|
@ -93,7 +95,7 @@ function ListFooterMaybeError({
|
|||
a.py_sm,
|
||||
]}
|
||||
onPress={onRetry}>
|
||||
Retry
|
||||
<Trans>Retry</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -128,121 +130,72 @@ export function ListMaybePlaceholder({
|
|||
isLoading,
|
||||
isEmpty,
|
||||
isError,
|
||||
empty,
|
||||
error,
|
||||
notFoundType = 'page',
|
||||
emptyTitle,
|
||||
emptyMessage,
|
||||
errorTitle,
|
||||
errorMessage,
|
||||
emptyType = 'page',
|
||||
onRetry,
|
||||
}: {
|
||||
isLoading: boolean
|
||||
isEmpty: boolean
|
||||
isError: boolean
|
||||
empty?: string
|
||||
error?: string
|
||||
notFoundType?: 'page' | 'results'
|
||||
isEmpty?: boolean
|
||||
isError?: boolean
|
||||
emptyTitle?: string
|
||||
emptyMessage?: string
|
||||
errorTitle?: string
|
||||
errorMessage?: string
|
||||
emptyType?: 'page' | 'results'
|
||||
onRetry?: () => Promise<unknown>
|
||||
}) {
|
||||
const navigation = useNavigationDeduped()
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {gtMobile, gtTablet} = useBreakpoints()
|
||||
const {_} = useLingui()
|
||||
|
||||
const canGoBack = navigation.canGoBack()
|
||||
const onGoBack = React.useCallback(() => {
|
||||
if (canGoBack) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('HomeTab')
|
||||
if (!isLoading && isError) {
|
||||
return (
|
||||
<Error
|
||||
title={errorTitle ?? _(msg`Oops!`)}
|
||||
message={errorMessage ?? _(`Something went wrong!`)}
|
||||
onRetry={onRetry}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Checking the state for routes ensures that web doesn't encounter errors while going back
|
||||
if (navigation.getState()?.routes) {
|
||||
navigation.dispatch(StackActions.push(...router.matchPath('/')))
|
||||
} else {
|
||||
navigation.navigate('HomeTab')
|
||||
navigation.dispatch(StackActions.popToTop())
|
||||
}
|
||||
}
|
||||
}, [navigation, canGoBack])
|
||||
|
||||
if (!isEmpty) return null
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.align_center,
|
||||
!gtMobile ? a.justify_between : a.gap_5xl,
|
||||
t.atoms.border_contrast_low,
|
||||
{paddingTop: 175, paddingBottom: 110},
|
||||
]}
|
||||
sideBorders={gtMobile}
|
||||
topBorder={!gtTablet}>
|
||||
{isLoading ? (
|
||||
if (isLoading) {
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.align_center,
|
||||
!gtMobile ? a.justify_between : a.gap_5xl,
|
||||
t.atoms.border_contrast_low,
|
||||
{paddingTop: 175, paddingBottom: 110},
|
||||
]}
|
||||
sideBorders={gtMobile}
|
||||
topBorder={!gtTablet}>
|
||||
<View style={[a.w_full, a.align_center, {top: 100}]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View style={[a.w_full, a.align_center, a.gap_lg]}>
|
||||
<Text style={[a.font_bold, a.text_3xl]}>
|
||||
{isError ? (
|
||||
<Trans>Oops!</Trans>
|
||||
) : isEmpty ? (
|
||||
<>
|
||||
{notFoundType === 'results' ? (
|
||||
<Trans>No results found</Trans>
|
||||
) : (
|
||||
<Trans>Page not found</Trans>
|
||||
)}
|
||||
</>
|
||||
) : undefined}
|
||||
</Text>
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
|
||||
{isError ? (
|
||||
<Text
|
||||
style={[a.text_md, a.text_center, t.atoms.text_contrast_high]}>
|
||||
{error ? error : <Trans>Something went wrong!</Trans>}
|
||||
</Text>
|
||||
) : isEmpty ? (
|
||||
<Text
|
||||
style={[a.text_md, a.text_center, t.atoms.text_contrast_high]}>
|
||||
{empty ? (
|
||||
empty
|
||||
) : (
|
||||
<Trans>
|
||||
We're sorry! We can't find the page you were looking for.
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
) : undefined}
|
||||
</View>
|
||||
<View
|
||||
style={[a.gap_md, !gtMobile ? [a.w_full, a.px_lg] : {width: 350}]}>
|
||||
{isError && onRetry && (
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
label="Click here"
|
||||
onPress={onRetry}
|
||||
size="large"
|
||||
style={[
|
||||
a.rounded_sm,
|
||||
a.overflow_hidden,
|
||||
{paddingVertical: 10},
|
||||
]}>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="solid"
|
||||
color={isError && onRetry ? 'secondary' : 'primary'}
|
||||
label="Click here"
|
||||
onPress={onGoBack}
|
||||
size="large"
|
||||
style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}>
|
||||
Go Back
|
||||
</Button>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</CenteredView>
|
||||
)
|
||||
if (isEmpty) {
|
||||
return (
|
||||
<Error
|
||||
title={
|
||||
emptyTitle ??
|
||||
(emptyType === 'results'
|
||||
? _(msg`No results found`)
|
||||
: _(msg`Page not found`))
|
||||
}
|
||||
message={
|
||||
emptyMessage ??
|
||||
_(msg`We're sorry! We can't find the page you were looking for.`)
|
||||
}
|
||||
onRetry={onRetry}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
ItemIconProps,
|
||||
} from '#/components/Menu/types'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {isNative} from 'platform/detection'
|
||||
|
||||
|
@ -209,7 +209,9 @@ function Cancel() {
|
|||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>Cancel</ButtonText>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,12 +5,13 @@ import {useLingui} from '@lingui/react'
|
|||
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||
|
||||
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
|
||||
import {getLabelingServiceTitle} from '#/lib/moderation'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {atoms as a, useTheme, useBreakpoints} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {Button, useButtonContext} from '#/components/Button'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||
import * as LabelingServiceCard from '#/components/LabelingServiceCard'
|
||||
|
||||
import {ReportDialogProps} from './types'
|
||||
|
||||
|
@ -22,31 +23,29 @@ export function SelectLabelerView({
|
|||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
return (
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.justify_center, a.gap_sm]}>
|
||||
<View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>Select moderation service</Trans>
|
||||
<Trans>Select moderator</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
<Trans>Who do you want to send this report to?</Trans>
|
||||
<Trans>To whom would you like to send this report?</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Divider />
|
||||
|
||||
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
|
||||
<View style={[a.gap_xs, {marginHorizontal: a.p_md.padding * -1}]}>
|
||||
{props.labelers.map(labeler => {
|
||||
return (
|
||||
<Button
|
||||
key={labeler.creator.did}
|
||||
label={_(msg`Send report to ${labeler.creator.displayName}`)}
|
||||
onPress={() => props.onSelectLabeler(labeler.creator.did)}>
|
||||
<LabelerButton
|
||||
title={labeler.creator.displayName || labeler.creator.handle}
|
||||
description={labeler.creator.description || ''}
|
||||
/>
|
||||
<LabelerButton labeler={labeler} />
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
|
@ -56,11 +55,9 @@ export function SelectLabelerView({
|
|||
}
|
||||
|
||||
function LabelerButton({
|
||||
title,
|
||||
description,
|
||||
labeler,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
labeler: AppBskyLabelerDefs.LabelerViewDetailed
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {hovered, pressed} = useButtonContext()
|
||||
|
@ -75,41 +72,21 @@ function LabelerButton({
|
|||
}, [t])
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.p_md,
|
||||
a.rounded_md,
|
||||
{paddingRight: 70},
|
||||
interacted && styles.interacted,
|
||||
]}>
|
||||
<View style={[a.flex_1, a.gap_xs]}>
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={[a.leading_tight, {maxWidth: 400}]} numberOfLines={3}>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.justify_center,
|
||||
a.pr_md,
|
||||
{left: 'auto'},
|
||||
]}>
|
||||
<ChevronRight
|
||||
size="md"
|
||||
fill={
|
||||
hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
|
||||
}
|
||||
<LabelingServiceCard.Outer
|
||||
style={[a.p_md, a.rounded_sm, interacted && styles.interacted]}>
|
||||
<LabelingServiceCard.Avatar avatar={labeler.creator.avatar} />
|
||||
<LabelingServiceCard.Content>
|
||||
<LabelingServiceCard.Title
|
||||
value={getLabelingServiceTitle({
|
||||
displayName: labeler.creator.displayName,
|
||||
handle: labeler.creator.handle,
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text
|
||||
style={[t.atoms.text_contrast_medium, a.text_sm, a.font_semibold]}>
|
||||
@{labeler.creator.handle}
|
||||
</Text>
|
||||
</LabelingServiceCard.Content>
|
||||
</LabelingServiceCard.Outer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {DMCA_LINK} from '#/components/ReportDialog/const'
|
|||
import {Link} from '#/components/Link'
|
||||
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {atoms as a, useTheme, useBreakpoints} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {
|
||||
Button,
|
||||
|
@ -35,6 +35,7 @@ export function SelectReportOptionView({
|
|||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const allReportOptions = useReportOptions()
|
||||
const reportOptions = allReportOptions[props.params.type]
|
||||
|
||||
|
@ -76,7 +77,7 @@ export function SelectReportOptionView({
|
|||
</Button>
|
||||
) : null}
|
||||
|
||||
<View style={[a.justify_center, a.gap_sm]}>
|
||||
<View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
{i18n.description}
|
||||
|
|
|
@ -264,7 +264,9 @@ export function TagMenu({
|
|||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>Cancel</ButtonText>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -1,48 +1,64 @@
|
|||
import React from 'react'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {Text} from '../Typography'
|
||||
import {DateInput} from '#/view/com/util/forms/DateInput'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
usePreferencesQuery,
|
||||
usePreferencesSetBirthDateMutation,
|
||||
UsePreferencesQueryResponse,
|
||||
} from '#/state/queries/preferences'
|
||||
import {Button, ButtonText} from '../Button'
|
||||
import {Button, ButtonIcon, ButtonText} from '../Button'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {ActivityIndicator, View} from 'react-native'
|
||||
import {isIOS, isWeb} from '#/platform/detection'
|
||||
import {Loader} from '#/components/Loader'
|
||||
|
||||
export function BirthDateSettingsDialog({
|
||||
control,
|
||||
preferences,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
preferences: UsePreferencesQueryResponse | undefined
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {isPending, isError, error, mutateAsync} =
|
||||
usePreferencesSetBirthDateMutation()
|
||||
const {isLoading, error, data: preferences} = usePreferencesQuery()
|
||||
|
||||
return (
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
|
||||
<Dialog.ScrollableInner label={_(msg`My Birthday`)}>
|
||||
{preferences && !isPending ? (
|
||||
<BirthdayInner
|
||||
control={control}
|
||||
preferences={preferences}
|
||||
isError={isError}
|
||||
error={error}
|
||||
setBirthDate={mutateAsync}
|
||||
<View style={[a.gap_sm, a.pb_lg]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>My Birthday</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
<Trans>This information is not shared with other users.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{isLoading ? (
|
||||
<Loader size="xl" />
|
||||
) : error || !preferences ? (
|
||||
<ErrorMessage
|
||||
message={
|
||||
error?.toString() ||
|
||||
_(
|
||||
msg`We were unable to load your birth date preferences. Please try again.`,
|
||||
)
|
||||
}
|
||||
style={[a.rounded_sm]}
|
||||
/>
|
||||
) : (
|
||||
<ActivityIndicator size="large" style={a.my_5xl} />
|
||||
<BirthdayInner control={control} preferences={preferences} />
|
||||
)}
|
||||
|
||||
<Dialog.Close />
|
||||
</Dialog.ScrollableInner>
|
||||
</Dialog.Outer>
|
||||
)
|
||||
|
@ -51,20 +67,18 @@ export function BirthDateSettingsDialog({
|
|||
function BirthdayInner({
|
||||
control,
|
||||
preferences,
|
||||
isError,
|
||||
error,
|
||||
setBirthDate,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
preferences: UsePreferencesQueryResponse
|
||||
isError: boolean
|
||||
error: unknown
|
||||
setBirthDate: (args: {birthDate: Date}) => Promise<unknown>
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const [date, setDate] = React.useState(preferences.birthDate || new Date())
|
||||
const t = useTheme()
|
||||
|
||||
const {
|
||||
isPending,
|
||||
isError,
|
||||
error,
|
||||
mutateAsync: setBirthDate,
|
||||
} = usePreferencesSetBirthDateMutation()
|
||||
const hasChanged = date !== preferences.birthDate
|
||||
|
||||
const onSave = React.useCallback(async () => {
|
||||
|
@ -74,21 +88,13 @@ function BirthdayInner({
|
|||
await setBirthDate({birthDate: date})
|
||||
}
|
||||
control.close()
|
||||
} catch (e) {
|
||||
logger.error(`setBirthDate failed`, {message: e})
|
||||
} catch (e: any) {
|
||||
logger.error(`setBirthDate failed`, {message: e.message})
|
||||
}
|
||||
}, [date, setBirthDate, control, hasChanged])
|
||||
|
||||
return (
|
||||
<View style={a.gap_lg} testID="birthDateSettingsDialog">
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>My Birthday</Trans>
|
||||
</Text>
|
||||
<Text style={t.atoms.text_contrast_medium}>
|
||||
<Trans>This information is not shared with other users.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={isIOS && [a.w_full, a.align_center]}>
|
||||
<DateInput
|
||||
handleAsUTC
|
||||
|
@ -103,6 +109,7 @@ function BirthdayInner({
|
|||
accessibilityLabelledBy="birthDate"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{isError ? (
|
||||
<ErrorMessage message={cleanError(error)} style={[a.rounded_sm]} />
|
||||
) : undefined}
|
||||
|
@ -110,13 +117,14 @@ function BirthdayInner({
|
|||
<View style={isWeb && [a.flex_row, a.justify_end]}>
|
||||
<Button
|
||||
label={hasChanged ? _(msg`Save birthday`) : _(msg`Done`)}
|
||||
size={isWeb ? 'small' : 'medium'}
|
||||
size="medium"
|
||||
onPress={onSave}
|
||||
variant="solid"
|
||||
color="primary">
|
||||
<ButtonText>
|
||||
{hasChanged ? <Trans>Save</Trans> : <Trans>Done</Trans>}
|
||||
</ButtonText>
|
||||
{isPending && <ButtonIcon icon={Loader} />}
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '#/state/queries/preferences'
|
||||
import {getLabelStrings} from '#/lib/moderation/useLabelInfo'
|
||||
|
||||
import {useTheme, atoms as a} from '#/alf'
|
||||
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {InlineLink} from '#/components/Link'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo'
|
||||
|
@ -29,6 +29,7 @@ export function ModerationLabelPref({
|
|||
}) {
|
||||
const {_, i18n} = useLingui()
|
||||
const t = useTheme()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
|
||||
const isGlobalLabel = !labelValueDefinition.definedBy
|
||||
const {identifier} = labelValueDefinition
|
||||
|
@ -57,6 +58,7 @@ export function ModerationLabelPref({
|
|||
adultOnly && !preferences?.moderationPrefs.adultContentEnabled
|
||||
// are there any reasons we cant configure this label here?
|
||||
const cantConfigure = isGlobalLabel || adultDisabled
|
||||
const showConfig = !disabled && (gtPhone || !cantConfigure)
|
||||
|
||||
// adjust the pref based on whether warn is available
|
||||
let prefAdjusted = pref
|
||||
|
@ -85,9 +87,19 @@ export function ModerationLabelPref({
|
|||
)
|
||||
|
||||
return (
|
||||
<View style={[a.flex_row, a.gap_sm, a.px_lg, a.py_lg, a.justify_between]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
a.justify_between,
|
||||
a.flex_wrap,
|
||||
]}>
|
||||
<View style={[a.gap_xs, a.flex_1]}>
|
||||
<Text style={[a.font_bold]}>{labelStrings.name}</Text>
|
||||
<Text style={[a.font_bold, gtPhone ? a.text_sm : a.text_md]}>
|
||||
{labelStrings.name}
|
||||
</Text>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||
{labelStrings.description}
|
||||
</Text>
|
||||
|
@ -113,40 +125,51 @@ export function ModerationLabelPref({
|
|||
</View>
|
||||
)}
|
||||
</View>
|
||||
{disabled ? (
|
||||
<></>
|
||||
) : cantConfigure ? (
|
||||
<View style={[{minHeight: 35}, a.px_sm, a.py_md]}>
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
{currentPrefLabel}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[{minHeight: 35}]}>
|
||||
<ToggleButton.Group
|
||||
label={_(
|
||||
msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`,
|
||||
)}
|
||||
values={[prefAdjusted]}
|
||||
onChange={newPref =>
|
||||
mutate({
|
||||
label: identifier,
|
||||
visibility: newPref[0] as LabelPreference,
|
||||
labelerDid,
|
||||
})
|
||||
}>
|
||||
<ToggleButton.Button name="ignore" label={ignoreLabel}>
|
||||
{ignoreLabel}
|
||||
</ToggleButton.Button>
|
||||
{canWarn && (
|
||||
<ToggleButton.Button name="warn" label={warnLabel}>
|
||||
{warnLabel}
|
||||
</ToggleButton.Button>
|
||||
)}
|
||||
<ToggleButton.Button name="hide" label={hideLabel}>
|
||||
{hideLabel}
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
|
||||
{showConfig && (
|
||||
<View style={[gtPhone ? undefined : a.w_full]}>
|
||||
{cantConfigure ? (
|
||||
<View
|
||||
style={[
|
||||
{minHeight: 35},
|
||||
a.px_md,
|
||||
a.py_md,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_low]}>
|
||||
{currentPrefLabel}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[{minHeight: 35}]}>
|
||||
<ToggleButton.Group
|
||||
label={_(
|
||||
msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`,
|
||||
)}
|
||||
values={[prefAdjusted]}
|
||||
onChange={newPref =>
|
||||
mutate({
|
||||
label: identifier,
|
||||
visibility: newPref[0] as LabelPreference,
|
||||
labelerDid,
|
||||
})
|
||||
}>
|
||||
<ToggleButton.Button name="ignore" label={ignoreLabel}>
|
||||
{ignoreLabel}
|
||||
</ToggleButton.Button>
|
||||
{canWarn && (
|
||||
<ToggleButton.Button name="warn" label={warnLabel}>
|
||||
{warnLabel}
|
||||
</ToggleButton.Button>
|
||||
)}
|
||||
<ToggleButton.Button name="hide" label={hideLabel}>
|
||||
{hideLabel}
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
|
|
@ -4,6 +4,23 @@ import TLDs from 'tlds'
|
|||
import psl from 'psl'
|
||||
|
||||
export const BSKY_APP_HOST = 'https://bsky.app'
|
||||
const BSKY_TRUSTED_HOSTS = [
|
||||
'bsky.app',
|
||||
'bsky.social',
|
||||
'blueskyweb.xyz',
|
||||
'blueskyweb.zendesk.com',
|
||||
...(__DEV__ ? ['localhost:19006', 'localhost:8100'] : []),
|
||||
]
|
||||
|
||||
/*
|
||||
* This will allow any BSKY_TRUSTED_HOSTS value by itself or with a subdomain.
|
||||
* It will also allow relative paths like /profile as well as #.
|
||||
*/
|
||||
const TRUSTED_REGEX = new RegExp(
|
||||
`^(http(s)?://(([\\w-]+\\.)?${BSKY_TRUSTED_HOSTS.join(
|
||||
'|([\\w-]+\\.)?',
|
||||
)})|/|#)`,
|
||||
)
|
||||
|
||||
export function isValidDomain(str: string): boolean {
|
||||
return !!TLDs.find(tld => {
|
||||
|
@ -86,6 +103,10 @@ export function isExternalUrl(url: string): boolean {
|
|||
return external || rss
|
||||
}
|
||||
|
||||
export function isTrustedUrl(url: string): boolean {
|
||||
return TRUSTED_REGEX.test(url)
|
||||
}
|
||||
|
||||
export function isBskyPostUrl(url: string): boolean {
|
||||
if (isBskyAppUrl(url)) {
|
||||
try {
|
||||
|
@ -163,8 +184,8 @@ export function feedUriToHref(url: string): string {
|
|||
export function linkRequiresWarning(uri: string, label: string) {
|
||||
const labelDomain = labelToDomain(label)
|
||||
|
||||
// If the uri started with a / we know it is internal.
|
||||
if (isRelativeUrl(uri)) {
|
||||
// We should trust any relative URL or a # since we know it links to internal content
|
||||
if (isRelativeUrl(uri) || uri === '#') {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -176,18 +197,11 @@ export function linkRequiresWarning(uri: string, label: string) {
|
|||
}
|
||||
|
||||
const host = urip.hostname.toLowerCase()
|
||||
// Hosts that end with bsky.app or bsky.social should be trusted by default.
|
||||
if (
|
||||
host.endsWith('bsky.app') ||
|
||||
host.endsWith('bsky.social') ||
|
||||
host.endsWith('blueskyweb.xyz')
|
||||
) {
|
||||
// if this is a link to internal content,
|
||||
// warn if it represents itself as a URL to another app
|
||||
if (isTrustedUrl(uri)) {
|
||||
// if this is a link to internal content, warn if it represents itself as a URL to another app
|
||||
return !!labelDomain && labelDomain !== host && isPossiblyAUrl(labelDomain)
|
||||
} else {
|
||||
// if this is a link to external content,
|
||||
// warn if the label doesnt match the target
|
||||
// if this is a link to external content, warn if the label doesnt match the target
|
||||
if (!labelDomain) {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -244,12 +244,12 @@ msgstr "Avançat"
|
|||
|
||||
#: src/view/screens/Feeds.tsx:666
|
||||
msgid "All the feeds you've saved, right in one place."
|
||||
msgstr ""
|
||||
msgstr "Tots els canals que has desat, en un sol lloc."
|
||||
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:221
|
||||
#: src/view/com/modals/ChangePassword.tsx:168
|
||||
msgid "Already have a code?"
|
||||
msgstr ""
|
||||
msgstr "Ja tens un codi?"
|
||||
|
||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:98
|
||||
msgid "Already signed in as @{0}"
|
||||
|
@ -287,7 +287,7 @@ msgstr "i"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:32
|
||||
msgid "Animals"
|
||||
msgstr ""
|
||||
msgstr "Animals"
|
||||
|
||||
#: src/view/screens/LanguageSettings.tsx:95
|
||||
msgid "App Language"
|
||||
|
@ -366,7 +366,7 @@ msgstr "Estàs escrivint en <0>{0}</0>?"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:26
|
||||
msgid "Art"
|
||||
msgstr ""
|
||||
msgstr "Art"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:123
|
||||
msgid "Artistic or non-erotic nudity."
|
||||
|
@ -393,7 +393,7 @@ msgstr "Endarrere"
|
|||
|
||||
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136
|
||||
msgid "Based on your interest in {interestsText}"
|
||||
msgstr ""
|
||||
msgstr "Segons els teus interessos en {interestsText}"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:523
|
||||
msgid "Basics"
|
||||
|
@ -472,7 +472,7 @@ msgstr "Bluesky"
|
|||
|
||||
#: src/view/com/auth/server-input/index.tsx:150
|
||||
msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers."
|
||||
msgstr ""
|
||||
msgstr "Bluesky és una xarxa oberta on pots escollir el teu proveïdor d'allotjament. L'allotjament personalitzat està disponible en beta per a desenvolupadors"
|
||||
|
||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80
|
||||
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:80
|
||||
|
@ -503,7 +503,7 @@ msgstr "Bluesky no mostrarà el teu perfil ni les publicacions als usuaris que n
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:33
|
||||
msgid "Books"
|
||||
msgstr ""
|
||||
msgstr "Llibres"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:859
|
||||
msgid "Build version {0} {1}"
|
||||
|
@ -631,11 +631,11 @@ msgstr "Canvia el meu correu"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:732
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
msgstr "Canvia la contrasenya"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:741
|
||||
msgid "Change Password"
|
||||
msgstr ""
|
||||
msgstr "Canvia la contrasenya"
|
||||
|
||||
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73
|
||||
msgid "Change post language to {0}"
|
||||
|
@ -643,7 +643,7 @@ msgstr "Canvia l'idioma de la publicació a {0}"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:733
|
||||
msgid "Change your Bluesky password"
|
||||
msgstr ""
|
||||
msgstr "Canvia la teva contrasenya de Bluesky"
|
||||
|
||||
#: src/view/com/modals/ChangeEmail.tsx:109
|
||||
msgid "Change Your Email"
|
||||
|
@ -652,7 +652,7 @@ msgstr "Canvia el teu correu"
|
|||
#: src/screens/Deactivated.tsx:72
|
||||
#: src/screens/Deactivated.tsx:76
|
||||
msgid "Check my status"
|
||||
msgstr ""
|
||||
msgstr "Comprova el meu estat"
|
||||
|
||||
#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121
|
||||
msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds."
|
||||
|
@ -680,7 +680,7 @@ msgstr "Tria un servei"
|
|||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:135
|
||||
msgid "Choose the algorithms that power your custom feeds."
|
||||
msgstr ""
|
||||
msgstr "Tria els algoritmes que alimentaran els teus canals personalitzats."
|
||||
|
||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83
|
||||
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:83
|
||||
|
@ -689,7 +689,7 @@ msgstr "Tria els algoritmes que potenciaran la teva experiència amb els canals
|
|||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103
|
||||
msgid "Choose your main feeds"
|
||||
msgstr ""
|
||||
msgstr "Tria els teus canals principals"
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:196
|
||||
msgid "Choose your password"
|
||||
|
@ -732,12 +732,12 @@ msgstr ""
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:35
|
||||
msgid "Climate"
|
||||
msgstr ""
|
||||
msgstr "Clima"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:265
|
||||
#: src/view/com/modals/ChangePassword.tsx:268
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Tanca"
|
||||
|
||||
#: src/components/Dialog/index.web.tsx:84
|
||||
#: src/components/Dialog/index.web.tsx:198
|
||||
|
@ -790,11 +790,11 @@ msgstr "Plega la llista d'usuaris per una notificació concreta"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:41
|
||||
msgid "Comedy"
|
||||
msgstr ""
|
||||
msgstr "Comèdia"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:27
|
||||
msgid "Comics"
|
||||
msgstr ""
|
||||
msgstr "Còmics"
|
||||
|
||||
#: src/Navigation.tsx:229
|
||||
#: src/view/screens/CommunityGuidelines.tsx:32
|
||||
|
@ -803,7 +803,7 @@ msgstr "Directrius de la comunitat"
|
|||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:148
|
||||
msgid "Complete onboarding and start using your account"
|
||||
msgstr ""
|
||||
msgstr "Finalitza el registre i comença a utilitzar el teu compte"
|
||||
|
||||
#: src/view/com/auth/create/Step3.tsx:73
|
||||
msgid "Complete the challenge"
|
||||
|
@ -819,7 +819,7 @@ msgstr "Redacta una resposta"
|
|||
|
||||
#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67
|
||||
msgid "Configure content filtering setting for category: {0}"
|
||||
msgstr ""
|
||||
msgstr "Configura els filtres de continguts per la categoria: {0}"
|
||||
|
||||
#: src/components/Prompt.tsx:124
|
||||
#: src/view/com/modals/AppealLabel.tsx:98
|
||||
|
@ -914,19 +914,19 @@ msgstr "Continua"
|
|||
#: src/screens/Onboarding/StepModeration/index.tsx:115
|
||||
#: src/screens/Onboarding/StepTopicalFeeds.tsx:111
|
||||
msgid "Continue to next step"
|
||||
msgstr ""
|
||||
msgstr "Continua"
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:167
|
||||
msgid "Continue to the next step"
|
||||
msgstr ""
|
||||
msgstr "Continua"
|
||||
|
||||
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191
|
||||
msgid "Continue to the next step without following any accounts"
|
||||
msgstr ""
|
||||
msgstr "Continua sense seguir cap compte"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:44
|
||||
msgid "Cooking"
|
||||
msgstr ""
|
||||
msgstr "Cuina"
|
||||
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:195
|
||||
#: src/view/com/modals/InviteCodes.tsx:182
|
||||
|
@ -1027,12 +1027,12 @@ msgstr "Crea una targeta amb una minuatura. La targeta enllaça a {url}"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:29
|
||||
msgid "Culture"
|
||||
msgstr ""
|
||||
msgstr "Cultura"
|
||||
|
||||
#: src/view/com/auth/server-input/index.tsx:95
|
||||
#: src/view/com/auth/server-input/index.tsx:96
|
||||
msgid "Custom"
|
||||
msgstr ""
|
||||
msgstr "Personalitzat"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:389
|
||||
msgid "Custom domain"
|
||||
|
@ -1041,7 +1041,7 @@ msgstr "Domini personalitzat"
|
|||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106
|
||||
#: src/view/screens/Feeds.tsx:692
|
||||
msgid "Custom feeds built by the community bring you new experiences and help you find the content you love."
|
||||
msgstr ""
|
||||
msgstr "Els canals personalitzats fets per la comunitat et porten noves experiències i t'ajuden a trobar contingut que t'agradarà."
|
||||
|
||||
#: src/view/screens/PreferencesExternalEmbeds.tsx:55
|
||||
msgid "Customize media from external sites."
|
||||
|
@ -1062,7 +1062,7 @@ msgstr "Mode fosc"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:498
|
||||
msgid "Dark Theme"
|
||||
msgstr ""
|
||||
msgstr "Tema fosc"
|
||||
|
||||
#: src/view/screens/Debug.tsx:83
|
||||
msgid "Debug panel"
|
||||
|
@ -1096,7 +1096,7 @@ msgstr "Elimina el meu compte"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:784
|
||||
msgid "Delete My Account…"
|
||||
msgstr ""
|
||||
msgstr "Elimina el meu compte…"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:317
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:326
|
||||
|
@ -1136,7 +1136,7 @@ msgstr "Vols dir alguna cosa?"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:504
|
||||
msgid "Dim"
|
||||
msgstr ""
|
||||
msgstr "Tènue"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:151
|
||||
msgid "Discard"
|
||||
|
@ -1161,7 +1161,7 @@ msgstr "Descobreix nous canals personalitzats"
|
|||
|
||||
#: src/view/screens/Feeds.tsx:689
|
||||
msgid "Discover New Feeds"
|
||||
msgstr ""
|
||||
msgstr "Descobreix nous canals"
|
||||
|
||||
#: src/view/com/modals/EditProfile.tsx:192
|
||||
msgid "Display name"
|
||||
|
@ -1218,20 +1218,20 @@ msgstr "Fes doble toc per iniciar la sessió"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:755
|
||||
msgid "Download Bluesky account data (repository)"
|
||||
msgstr ""
|
||||
msgstr "Descarrega les dades del compte de Bluesky (repositori)"
|
||||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:59
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:63
|
||||
msgid "Download CAR file"
|
||||
msgstr ""
|
||||
msgstr "Descarrega el fitxer CAR"
|
||||
|
||||
#: src/view/com/composer/text-input/TextInput.web.tsx:249
|
||||
msgid "Drop to add images"
|
||||
msgstr ""
|
||||
msgstr "Deixa anar per afegir imatges"
|
||||
|
||||
#: 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 ""
|
||||
msgstr "Degut a les polítiques d'Apple, el contingut per a adults només es pot habilitar a la web després de registrar-se"
|
||||
|
||||
#: src/view/com/modals/EditProfile.tsx:185
|
||||
msgid "e.g. Alice Roberts"
|
||||
|
@ -1316,7 +1316,7 @@ msgstr "Edita la descripció del teu perfil"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:34
|
||||
msgid "Education"
|
||||
msgstr ""
|
||||
msgstr "Ensenyament"
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:176
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:156
|
||||
|
@ -1357,7 +1357,7 @@ msgstr "Habilita el contingut per a adults"
|
|||
#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76
|
||||
#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77
|
||||
msgid "Enable adult content in your feeds"
|
||||
msgstr ""
|
||||
msgstr "Habilita veure el contingut per adults als teus canals"
|
||||
|
||||
#: src/view/com/modals/EmbedConsent.tsx:97
|
||||
msgid "Enable External Media"
|
||||
|
@ -1394,7 +1394,7 @@ msgstr "Entra el codi de confirmació"
|
|||
|
||||
#: src/view/com/modals/ChangePassword.tsx:151
|
||||
msgid "Enter the code you received to change your password."
|
||||
msgstr ""
|
||||
msgstr "Introdueix el codi que has rebut per canviar la teva contrasenya."
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:371
|
||||
msgid "Enter the domain you want to use"
|
||||
|
@ -1473,12 +1473,12 @@ msgstr "Expandeix o replega la publicació completa a la qual estàs responent"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:753
|
||||
msgid "Export my data"
|
||||
msgstr ""
|
||||
msgstr "Exporta les meves dades"
|
||||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:44
|
||||
#: src/view/screens/Settings/index.tsx:764
|
||||
msgid "Export My Data"
|
||||
msgstr ""
|
||||
msgstr "Exporta les meves dades"
|
||||
|
||||
#: src/view/com/modals/EmbedConsent.tsx:64
|
||||
msgid "External Media"
|
||||
|
@ -1559,11 +1559,11 @@ msgstr "Els canals són algoritmes personalitzats creats per usuaris que coneixe
|
|||
|
||||
#: src/screens/Onboarding/StepTopicalFeeds.tsx:76
|
||||
msgid "Feeds can be topical as well!"
|
||||
msgstr ""
|
||||
msgstr "Els canals també poden ser d'actualitat!"
|
||||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:151
|
||||
msgid "Finalizing"
|
||||
msgstr ""
|
||||
msgstr "Finalitzant"
|
||||
|
||||
#: src/view/com/posts/CustomFeedEmptyState.tsx:47
|
||||
#: src/view/com/posts/FollowingEmptyState.tsx:57
|
||||
|
@ -1597,11 +1597,11 @@ msgstr "Ajusta els fils de debat."
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:38
|
||||
msgid "Fitness"
|
||||
msgstr ""
|
||||
msgstr "Exercici"
|
||||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:131
|
||||
msgid "Flexible"
|
||||
msgstr ""
|
||||
msgstr "Flexible"
|
||||
|
||||
#: src/view/com/modals/EditImage.tsx:115
|
||||
msgid "Flip horizontal"
|
||||
|
@ -1631,11 +1631,11 @@ msgstr "Segueix {0}"
|
|||
|
||||
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179
|
||||
msgid "Follow All"
|
||||
msgstr ""
|
||||
msgstr "Segueix-los a tots"
|
||||
|
||||
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174
|
||||
msgid "Follow selected accounts and continue to the next step"
|
||||
msgstr ""
|
||||
msgstr "Segueix els comptes seleccionats i continua"
|
||||
|
||||
#: 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."
|
||||
|
@ -1693,7 +1693,7 @@ msgstr "Et segueix"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:43
|
||||
msgid "Food"
|
||||
msgstr ""
|
||||
msgstr "Menjar"
|
||||
|
||||
#: src/view/com/modals/DeleteAccount.tsx:111
|
||||
msgid "For security reasons, we'll need to send a confirmation code to your email address."
|
||||
|
@ -1752,7 +1752,7 @@ msgstr "Ves enrere"
|
|||
#: src/screens/Onboarding/Layout.tsx:104
|
||||
#: src/screens/Onboarding/Layout.tsx:193
|
||||
msgid "Go back to previous step"
|
||||
msgstr ""
|
||||
msgstr "Ves al pas anterior"
|
||||
|
||||
#: src/view/screens/Search/Search.tsx:747
|
||||
#: src/view/shell/desktop/Search.tsx:262
|
||||
|
@ -1794,15 +1794,15 @@ msgstr "Ajuda"
|
|||
|
||||
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132
|
||||
msgid "Here are some accounts for you to follow"
|
||||
msgstr ""
|
||||
msgstr "Aquí tens uns quants comptes que pots seguir"
|
||||
|
||||
#: src/screens/Onboarding/StepTopicalFeeds.tsx:85
|
||||
msgid "Here are some popular topical feeds. You can choose to follow as many as you like."
|
||||
msgstr ""
|
||||
msgstr "Aquí tens alguns canals d'actualitat populars. Pots seguir-ne tants com vulguis."
|
||||
|
||||
#: src/screens/Onboarding/StepTopicalFeeds.tsx:80
|
||||
msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like."
|
||||
msgstr ""
|
||||
msgstr "Aquí tens uns quants canals d'actualitat basats en els teus interesos: {interestsText}. Pots seguir-ne tants com vulguis."
|
||||
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:153
|
||||
msgid "Here is your app password."
|
||||
|
@ -1914,7 +1914,7 @@ msgstr "Si no en selecciones cap, és apropiat per a totes les edats."
|
|||
|
||||
#: 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 "Si vols canviar la contrasenya t'enviarem un codi per verificar que aquest compte és teu."
|
||||
|
||||
#: src/view/com/util/images/Gallery.tsx:38
|
||||
msgid "Image"
|
||||
|
@ -2024,7 +2024,7 @@ msgstr "Codis d'invitació: 1 disponible"
|
|||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:64
|
||||
msgid "It shows posts from the people you follow as they happen."
|
||||
msgstr ""
|
||||
msgstr "Mostra les publicacions de les persones que segueixes cronològicament."
|
||||
|
||||
#: src/view/com/auth/HomeLoggedOutCTA.tsx:99
|
||||
#: src/view/com/auth/SplashScreen.web.tsx:138
|
||||
|
@ -2046,7 +2046,7 @@ msgstr "Feines"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:24
|
||||
msgid "Journalism"
|
||||
msgstr ""
|
||||
msgstr "Periodisme"
|
||||
|
||||
#: src/view/com/composer/select-language/SelectLangBtn.tsx:104
|
||||
msgid "Language selection"
|
||||
|
@ -2101,7 +2101,7 @@ msgstr "Sortint de Bluesky"
|
|||
|
||||
#: src/screens/Deactivated.tsx:128
|
||||
msgid "left to go."
|
||||
msgstr ""
|
||||
msgstr "queda."
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:278
|
||||
msgid "Legacy storage cleared, you need to restart the app now."
|
||||
|
@ -2114,7 +2114,7 @@ msgstr "Restablirem la teva contrasenya!"
|
|||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:151
|
||||
msgid "Let's go!"
|
||||
msgstr ""
|
||||
msgstr "Som-hi!"
|
||||
|
||||
#: src/view/com/util/UserAvatar.tsx:248
|
||||
#: src/view/com/util/UserBanner.tsx:62
|
||||
|
@ -2140,7 +2140,7 @@ msgstr "Li ha agradat a"
|
|||
#: src/view/screens/PostLikedBy.tsx:27
|
||||
#: src/view/screens/ProfileFeedLikedBy.tsx:27
|
||||
msgid "Liked By"
|
||||
msgstr ""
|
||||
msgstr "Li ha agradat a"
|
||||
|
||||
#: src/view/com/feeds/FeedSourceCard.tsx:279
|
||||
msgid "Liked by {0} {1}"
|
||||
|
@ -2152,7 +2152,7 @@ msgstr "Li ha agradat a {likeCount} {0}"
|
|||
|
||||
#: src/view/com/notifications/FeedItem.tsx:170
|
||||
msgid "liked your custom feed"
|
||||
msgstr ""
|
||||
msgstr "els hi ha agradat el teu canal personalitzat"
|
||||
|
||||
#: src/view/com/notifications/FeedItem.tsx:171
|
||||
#~ msgid "liked your custom feed{0}"
|
||||
|
@ -2247,7 +2247,7 @@ msgstr "Registre"
|
|||
#: src/screens/Deactivated.tsx:178
|
||||
#: src/screens/Deactivated.tsx:181
|
||||
msgid "Log out"
|
||||
msgstr ""
|
||||
msgstr "Desconnecta"
|
||||
|
||||
#: src/view/screens/Moderation.tsx:155
|
||||
msgid "Logged-out visibility"
|
||||
|
@ -2477,7 +2477,7 @@ msgstr "Els meus canals desats"
|
|||
|
||||
#: src/view/com/auth/server-input/index.tsx:118
|
||||
msgid "my-server.com"
|
||||
msgstr ""
|
||||
msgstr "el-meu-servidor.com"
|
||||
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:179
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:290
|
||||
|
@ -2490,7 +2490,7 @@ msgstr "Es requereix un nom"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:25
|
||||
msgid "Nature"
|
||||
msgstr ""
|
||||
msgstr "Natura"
|
||||
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:190
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:219
|
||||
|
@ -2516,7 +2516,7 @@ msgstr "No perdis mai accés als teus seguidors ni a les teves dades."
|
|||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:119
|
||||
msgid "Never lose access to your followers or data."
|
||||
msgstr ""
|
||||
msgstr "No perdis mai accés als teus seguidors i les teves dades."
|
||||
|
||||
#: src/components/dialogs/MutedWords.tsx:293
|
||||
msgid "Nevermind"
|
||||
|
@ -2541,7 +2541,7 @@ msgstr "Nova contrasenya"
|
|||
|
||||
#: src/view/com/modals/ChangePassword.tsx:215
|
||||
msgid "New Password"
|
||||
msgstr ""
|
||||
msgstr "Nova contrasenya"
|
||||
|
||||
#: src/view/com/feeds/FeedPage.tsx:126
|
||||
msgctxt "action"
|
||||
|
@ -2577,7 +2577,7 @@ msgstr "Les respostes més noves primer"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:23
|
||||
msgid "News"
|
||||
msgstr ""
|
||||
msgstr "Notícies"
|
||||
|
||||
#: src/view/com/auth/create/CreateAccount.tsx:172
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:182
|
||||
|
@ -2687,7 +2687,7 @@ msgstr "Ostres!"
|
|||
|
||||
#: src/screens/Onboarding/StepInterests/index.tsx:128
|
||||
msgid "Oh no! Something went wrong."
|
||||
msgstr ""
|
||||
msgstr "Ostres! Alguna cosa ha fallat."
|
||||
|
||||
#: src/view/com/auth/login/PasswordUpdatedForm.tsx:41
|
||||
msgid "Okay"
|
||||
|
@ -2721,7 +2721,7 @@ msgstr "Ostres!"
|
|||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:115
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
msgstr "Obre"
|
||||
|
||||
#: src/view/screens/Moderation.tsx:75
|
||||
msgid "Open content filtering settings"
|
||||
|
@ -2750,7 +2750,7 @@ msgstr ""
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:804
|
||||
msgid "Open storybook page"
|
||||
msgstr ""
|
||||
msgstr "Obre la pàgina d'historial"
|
||||
|
||||
#: src/view/com/util/forms/DropdownButton.tsx:154
|
||||
msgid "Opens {numItems} options"
|
||||
|
@ -2876,7 +2876,7 @@ msgstr "Pàgina no trobada"
|
|||
|
||||
#: src/view/screens/NotFound.tsx:42
|
||||
msgid "Page Not Found"
|
||||
msgstr ""
|
||||
msgstr "Pàgina no trobada"
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:191
|
||||
#: src/view/com/auth/create/Step1.tsx:201
|
||||
|
@ -2912,7 +2912,7 @@ msgstr "S'ha denegat el permís per accedir a la càmera. Activa'l a la configur
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:31
|
||||
msgid "Pets"
|
||||
msgstr ""
|
||||
msgstr "Mascotes"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:183
|
||||
#~ msgid "Phone number"
|
||||
|
@ -3010,7 +3010,7 @@ msgstr "Espera que es generi la targeta de l'enllaç"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:37
|
||||
msgid "Politics"
|
||||
msgstr ""
|
||||
msgstr "Política"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:111
|
||||
msgid "Porn"
|
||||
|
@ -3129,7 +3129,7 @@ msgstr "Protegeix el teu compte verificant el teu correu."
|
|||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:101
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
msgstr "Públic"
|
||||
|
||||
#: src/view/screens/ModerationModlists.tsx:61
|
||||
msgid "Public, shareable lists of users to mute or block in bulk."
|
||||
|
@ -3300,7 +3300,7 @@ msgstr "Informa de la publicació"
|
|||
#: src/view/com/util/post-ctrls/RepostButton.tsx:61
|
||||
msgctxt "action"
|
||||
msgid "Repost"
|
||||
msgstr "Respon"
|
||||
msgstr "Republica"
|
||||
|
||||
#: src/view/com/util/post-ctrls/RepostButton.web.tsx:48
|
||||
msgid "Repost"
|
||||
|
@ -3317,11 +3317,11 @@ msgstr "Republica o cita la publicació"
|
|||
|
||||
#: src/view/screens/PostRepostedBy.tsx:27
|
||||
msgid "Reposted By"
|
||||
msgstr ""
|
||||
msgstr "Republicat per"
|
||||
|
||||
#: src/view/com/posts/FeedItem.tsx:207
|
||||
msgid "Reposted by {0}"
|
||||
msgstr ""
|
||||
msgstr "Republicat per {0}"
|
||||
|
||||
#: src/view/com/posts/FeedItem.tsx:206
|
||||
#~ msgid "Reposted by {0})"
|
||||
|
@ -3351,7 +3351,7 @@ msgstr "Demana un canvi"
|
|||
#: src/view/com/modals/ChangePassword.tsx:239
|
||||
#: src/view/com/modals/ChangePassword.tsx:241
|
||||
msgid "Request Code"
|
||||
msgstr ""
|
||||
msgstr "Demana un codi"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:456
|
||||
msgid "Require alt text before posting"
|
||||
|
@ -3368,7 +3368,7 @@ msgstr "Codi de restabliment"
|
|||
|
||||
#: src/view/com/modals/ChangePassword.tsx:190
|
||||
msgid "Reset Code"
|
||||
msgstr ""
|
||||
msgstr "Codi de restabliment"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:824
|
||||
msgid "Reset onboarding"
|
||||
|
@ -3475,7 +3475,7 @@ msgstr "Desa el canvi d'identificador a {handle}"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:36
|
||||
msgid "Science"
|
||||
msgstr ""
|
||||
msgstr "Ciència"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:859
|
||||
msgid "Scroll to top"
|
||||
|
@ -3584,19 +3584,19 @@ msgstr "Selecciona el servei"
|
|||
|
||||
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52
|
||||
msgid "Select some accounts below to follow"
|
||||
msgstr ""
|
||||
msgstr "Selecciona alguns d'aquests comptes per seguir-los"
|
||||
|
||||
#: src/view/com/auth/server-input/index.tsx:82
|
||||
msgid "Select the service that hosts your data."
|
||||
msgstr ""
|
||||
msgstr "Selecciona el servei que allotja les teves dades."
|
||||
|
||||
#: src/screens/Onboarding/StepTopicalFeeds.tsx:96
|
||||
msgid "Select topical feeds to follow from the list below"
|
||||
msgstr ""
|
||||
msgstr "Selecciona els canals d'actualitat per seguir d'aquesta llista"
|
||||
|
||||
#: src/screens/Onboarding/StepModeration/index.tsx:75
|
||||
msgid "Select what you want to see (or not see), and we’ll handle the rest."
|
||||
msgstr ""
|
||||
msgstr "Selecciona què vols veure (o què no vols veure) i nosaltres farem la resta."
|
||||
|
||||
#: src/view/screens/LanguageSettings.tsx:281
|
||||
msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown."
|
||||
|
@ -3608,7 +3608,7 @@ msgstr "Selecciona l'idioma de l'aplicació perquè el text predeterminat es mos
|
|||
|
||||
#: src/screens/Onboarding/StepInterests/index.tsx:196
|
||||
msgid "Select your interests from the options below"
|
||||
msgstr ""
|
||||
msgstr "Selecciona els teus interesos d'entre aquestes opcions"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:155
|
||||
#~ msgid "Select your phone's country"
|
||||
|
@ -3620,11 +3620,11 @@ msgstr "Selecciona el teu idioma preferit per a les traduccions al teu canal."
|
|||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:116
|
||||
msgid "Select your primary algorithmic feeds"
|
||||
msgstr ""
|
||||
msgstr "Selecciona els teus canals algorítmics primaris"
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:142
|
||||
msgid "Select your secondary algorithmic feeds"
|
||||
msgstr ""
|
||||
msgstr "Selecciona els teus canals algorítmics secundaris"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:202
|
||||
#: src/view/com/modals/VerifyEmail.tsx:204
|
||||
|
@ -3659,7 +3659,7 @@ msgstr "Envia un correu amb el codi de confirmació per l'eliminació del compte
|
|||
|
||||
#: src/view/com/auth/server-input/index.tsx:110
|
||||
msgid "Server address"
|
||||
msgstr ""
|
||||
msgstr "Adreça del servidor"
|
||||
|
||||
#: src/view/com/modals/ContentFilteringSettings.tsx:311
|
||||
msgid "Set {value} for {labelGroup} content moderation policy"
|
||||
|
@ -3685,11 +3685,11 @@ msgstr "Estableix el tema de colors a la configuració del sistema"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:514
|
||||
msgid "Set dark theme to the dark theme"
|
||||
msgstr ""
|
||||
msgstr "Posa el tema fosc"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:507
|
||||
msgid "Set dark theme to the dim theme"
|
||||
msgstr ""
|
||||
msgstr "Posa el tema fosc al tema atenuat"
|
||||
|
||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:104
|
||||
msgid "Set new password"
|
||||
|
@ -3725,7 +3725,7 @@ msgstr ""
|
|||
|
||||
#: src/screens/Onboarding/Layout.tsx:50
|
||||
msgid "Set up your account"
|
||||
msgstr ""
|
||||
msgstr "Configura el teu compte"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:266
|
||||
msgid "Sets Bluesky username"
|
||||
|
@ -3813,15 +3813,15 @@ msgstr "Mostra les publicacions citades"
|
|||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:118
|
||||
msgid "Show quote-posts in Following feed"
|
||||
msgstr ""
|
||||
msgstr "Mostra les publicacions citades en el canal Seguint"
|
||||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:134
|
||||
msgid "Show quotes in Following"
|
||||
msgstr ""
|
||||
msgstr "Mostra els citats a Seguint"
|
||||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:94
|
||||
msgid "Show re-posts in Following feed"
|
||||
msgstr ""
|
||||
msgstr "Mostra les republicacions al canal Seguint"
|
||||
|
||||
#: src/view/screens/PreferencesFollowingFeed.tsx:119
|
||||
msgid "Show Replies"
|
||||
|
@ -3833,11 +3833,11 @@ msgstr "Mostra les respostes dels comptes que segueixes abans que les altres."
|
|||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:86
|
||||
msgid "Show replies in Following"
|
||||
msgstr ""
|
||||
msgstr "Mostra les respostes a Seguint"
|
||||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:70
|
||||
msgid "Show replies in Following feed"
|
||||
msgstr ""
|
||||
msgstr "Mostrea les respostes al canal Seguint"
|
||||
|
||||
#: src/view/screens/PreferencesFollowingFeed.tsx:70
|
||||
msgid "Show replies with at least {value} {0}"
|
||||
|
@ -3849,7 +3849,7 @@ msgstr "Mostra republicacions"
|
|||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:110
|
||||
msgid "Show reposts in Following"
|
||||
msgstr ""
|
||||
msgstr "Mostra les republicacions al canal Seguint"
|
||||
|
||||
#: src/view/com/util/moderation/ContentHider.tsx:67
|
||||
#: src/view/com/util/moderation/PostHider.tsx:61
|
||||
|
@ -3949,7 +3949,7 @@ msgstr "Salta aquest pas"
|
|||
|
||||
#: src/screens/Onboarding/StepInterests/index.tsx:232
|
||||
msgid "Skip this flow"
|
||||
msgstr ""
|
||||
msgstr "Salta aquest flux"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:82
|
||||
#~ msgid "SMS verification"
|
||||
|
@ -3957,7 +3957,7 @@ msgstr ""
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:40
|
||||
msgid "Software Dev"
|
||||
msgstr ""
|
||||
msgstr "Desenvolupament de programari"
|
||||
|
||||
#: src/view/com/modals/ProfilePreview.tsx:62
|
||||
#~ msgid "Something went wrong and we're not sure what."
|
||||
|
@ -3985,7 +3985,7 @@ msgstr "Ordena les respostes a la mateixa publicació per:"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:30
|
||||
msgid "Sports"
|
||||
msgstr ""
|
||||
msgstr "Esports"
|
||||
|
||||
#: src/view/com/modals/crop-image/CropImage.web.tsx:122
|
||||
msgid "Square"
|
||||
|
@ -4023,7 +4023,7 @@ msgstr "Subscriure's"
|
|||
#: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:308
|
||||
msgid "Subscribe to the {0} feed"
|
||||
msgstr ""
|
||||
msgstr "Subscriu-te al canal {0}"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:604
|
||||
msgid "Subscribe to this list"
|
||||
|
@ -4095,7 +4095,7 @@ msgstr "Toca per veure-ho completament"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:39
|
||||
msgid "Tech"
|
||||
msgstr ""
|
||||
msgstr "Tecnologia"
|
||||
|
||||
#: src/view/shell/desktop/RightNav.tsx:81
|
||||
msgid "Terms"
|
||||
|
@ -4135,7 +4135,7 @@ msgstr "La política de drets d'autoria ha estat traslladada a <0/>"
|
|||
|
||||
#: src/screens/Onboarding/Layout.tsx:60
|
||||
msgid "The following steps will help customize your Bluesky experience."
|
||||
msgstr ""
|
||||
msgstr "Els següents passos t'ajudaran a personalitzar la teva experiència a Bluesky."
|
||||
|
||||
#: src/view/com/post-thread/PostThread.tsx:517
|
||||
msgid "The post may have been deleted."
|
||||
|
@ -4159,7 +4159,7 @@ msgstr "Les condicions del servei han estat traslladades a "
|
|||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:150
|
||||
msgid "There are many feeds to try:"
|
||||
msgstr ""
|
||||
msgstr "Hi ha molts canals per provar:"
|
||||
|
||||
#: src/view/screens/ProfileFeed.tsx:550
|
||||
msgid "There was an an issue contacting the server, please check your internet connection and try again."
|
||||
|
@ -4239,7 +4239,7 @@ msgstr "S'ha produït un problema inesperat a l'aplicació. Fes-nos saber si aix
|
|||
|
||||
#: src/screens/Deactivated.tsx:106
|
||||
msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can."
|
||||
msgstr ""
|
||||
msgstr "Hi ha hagut una gran quantitat d'usuaris nous a Bluesky! Activarem el teu compte tan aviat com puguem."
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:55
|
||||
#~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!"
|
||||
|
@ -4247,7 +4247,7 @@ msgstr ""
|
|||
|
||||
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138
|
||||
msgid "These are popular accounts you might like:"
|
||||
msgstr ""
|
||||
msgstr "Aquests són alguns comptes populars que et poden agradar:"
|
||||
|
||||
#~ msgid "This {0} has been labeled."
|
||||
#~ msgstr "Aquest {0} ha estat etiquetat."
|
||||
|
@ -4274,7 +4274,7 @@ msgstr "Aquest contingut no es pot veure sense un compte de Bluesky."
|
|||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:75
|
||||
msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>"
|
||||
msgstr ""
|
||||
msgstr "Aquesta funcionalitat està en beta. En <0>aquesta entrada al blog</0> tens més informació."
|
||||
|
||||
#: src/view/com/posts/FeedErrorMessage.tsx:114
|
||||
msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later."
|
||||
|
@ -4328,7 +4328,7 @@ msgstr "Aquest usuari està inclós a la llista <0/> que tens bloquejada"
|
|||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:74
|
||||
msgid "This user is included in the <0/> list which you have muted."
|
||||
msgstr ""
|
||||
msgstr "Aquest usuari està inclòs a la llista <0/> que has silenciat."
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:74
|
||||
#~ msgid "This user is included the <0/> list which you have muted."
|
||||
|
@ -4614,7 +4614,7 @@ msgstr "Verifica el teu correu"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:42
|
||||
msgid "Video Games"
|
||||
msgstr ""
|
||||
msgstr "Videojocs"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:662
|
||||
msgid "View {0}'s avatar"
|
||||
|
@ -4647,7 +4647,7 @@ msgstr "Adverteix"
|
|||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134
|
||||
msgid "We also think you'll like \"For You\" by Skygaze:"
|
||||
msgstr ""
|
||||
msgstr "També creiem que t'agradarà el canal \"For You\" d'Skygaze:"
|
||||
|
||||
#: src/screens/Hashtag.tsx:132
|
||||
msgid "We couldn't find any results for that hashtag."
|
||||
|
@ -4655,11 +4655,11 @@ msgstr ""
|
|||
|
||||
#: src/screens/Deactivated.tsx:133
|
||||
msgid "We estimate {estimatedTime} until your account is ready."
|
||||
msgstr ""
|
||||
msgstr "Calculem {estimatedTime} fins que el teu compte estigui llest."
|
||||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:93
|
||||
msgid "We hope you have a wonderful time. Remember, Bluesky is:"
|
||||
msgstr ""
|
||||
msgstr "Esperem que t'ho passis pipa. Recorda que Bluesky és:"
|
||||
|
||||
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||
msgid "We ran out of posts from your follows. Here's the latest from <0/>."
|
||||
|
@ -4671,15 +4671,15 @@ msgstr ""
|
|||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124
|
||||
msgid "We recommend our \"Discover\" feed:"
|
||||
msgstr ""
|
||||
msgstr "Et reomanem el nostre canal \"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."
|
||||
msgstr ""
|
||||
msgstr "No ens hem pogut connectar. Torna-ho a provar per continuar configurant el teu compte. Si continua fallant, pots ometre aquest flux."
|
||||
|
||||
#: src/screens/Deactivated.tsx:137
|
||||
msgid "We will let you know when your account is ready."
|
||||
msgstr ""
|
||||
msgstr "T'informarem quan el teu compte estigui llest."
|
||||
|
||||
#: src/view/com/modals/AppealLabel.tsx:48
|
||||
msgid "We'll look into your appeal promptly."
|
||||
|
@ -4687,7 +4687,7 @@ msgstr "Analitzarem la teva apel·lació ràpidament."
|
|||
|
||||
#: src/screens/Onboarding/StepInterests/index.tsx:138
|
||||
msgid "We'll use this to help customize your experience."
|
||||
msgstr ""
|
||||
msgstr "Ho farem servir per personalitzar la teva experiència."
|
||||
|
||||
#: src/view/com/auth/create/CreateAccount.tsx:134
|
||||
msgid "We're so excited to have you join us!"
|
||||
|
@ -4716,7 +4716,7 @@ msgstr "Benvingut a <0>Bluesky</0>"
|
|||
|
||||
#: src/screens/Onboarding/StepInterests/index.tsx:130
|
||||
msgid "What are your interests?"
|
||||
msgstr ""
|
||||
msgstr "Quins són els teus interesos?"
|
||||
|
||||
#: src/view/com/modals/report/Modal.tsx:169
|
||||
msgid "What is the issue with this {collectionName}?"
|
||||
|
@ -4758,7 +4758,7 @@ msgstr "Escriu la teva resposta"
|
|||
|
||||
#: src/screens/Onboarding/index.tsx:28
|
||||
msgid "Writers"
|
||||
msgstr ""
|
||||
msgstr "Escriptors"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:263
|
||||
#~ msgid "XXXXXX"
|
||||
|
@ -4776,7 +4776,7 @@ msgstr "Sí"
|
|||
|
||||
#: src/screens/Deactivated.tsx:130
|
||||
msgid "You are in line."
|
||||
msgstr ""
|
||||
msgstr "Estàs a la cua."
|
||||
|
||||
#: src/view/com/posts/FollowingEmptyState.tsx:67
|
||||
#: src/view/com/posts/FollowingEndOfFeed.tsx:68
|
||||
|
@ -4789,7 +4789,7 @@ msgstr "També pots descobrir nous canals personalitzats per seguir."
|
|||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:142
|
||||
msgid "You can change these settings later."
|
||||
msgstr ""
|
||||
msgstr "Pots canviar aquests paràmetres més endavant."
|
||||
|
||||
#: src/view/com/auth/login/Login.tsx:158
|
||||
#: src/view/com/auth/login/PasswordUpdatedForm.tsx:31
|
||||
|
@ -4825,7 +4825,7 @@ msgstr "Has bloquejat aquest usuari. No pots veure el seu contingut."
|
|||
#: 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 "Has entrat un codi invàlid. Hauria de ser tipus XXXXX-XXXXX."
|
||||
|
||||
#: src/view/com/modals/ModerationDetails.tsx:87
|
||||
msgid "You have muted this user."
|
||||
|
@ -4862,7 +4862,7 @@ msgstr "Has de tenir 18 anys o més per habilitar el contingut per a adults."
|
|||
|
||||
#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103
|
||||
msgid "You must be 18 years or older to enable adult content"
|
||||
msgstr ""
|
||||
msgstr "Has de tenir 18 anys o més per habilitar el contingut per a adults"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:147
|
||||
msgid "You will no longer receive notifications for this thread"
|
||||
|
@ -4878,17 +4878,17 @@ msgstr "Rebràs un correu amb un \"codi de restabliment\". Introdueix aquí el c
|
|||
|
||||
#: src/screens/Onboarding/StepModeration/index.tsx:72
|
||||
msgid "You're in control"
|
||||
msgstr ""
|
||||
msgstr "Tu tens el control"
|
||||
|
||||
#: src/screens/Deactivated.tsx:87
|
||||
#: src/screens/Deactivated.tsx:88
|
||||
#: src/screens/Deactivated.tsx:103
|
||||
msgid "You're in line"
|
||||
msgstr ""
|
||||
msgstr "Estàs a la cua"
|
||||
|
||||
#: src/screens/Onboarding/StepFinished.tsx:90
|
||||
msgid "You're ready to go!"
|
||||
msgstr ""
|
||||
msgstr "Ja està tot llest!"
|
||||
|
||||
#: src/view/com/posts/FollowingEndOfFeed.tsx:48
|
||||
msgid "You've reached the end of your feed! Find some more accounts to follow."
|
||||
|
@ -4904,7 +4904,7 @@ msgstr "El teu compte s'ha eliminat"
|
|||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:47
|
||||
msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately."
|
||||
msgstr ""
|
||||
msgstr "El repositori del teu compte, que conté tots els registres de dades públiques, es pot baixar com a fitxer \"CAR\". Aquest fitxer no inclou incrustacions multimèdia, com ara imatges, ni les teves dades privades, que s'han d'obtenir per separat."
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:215
|
||||
msgid "Your birth date"
|
||||
|
@ -4916,7 +4916,7 @@ msgstr "La teva elecció es desarà, però es pot canviar més endavant a la con
|
|||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:61
|
||||
msgid "Your default feed is \"Following\""
|
||||
msgstr ""
|
||||
msgstr "El teu canal per defecte és \"Seguint\""
|
||||
|
||||
#: src/view/com/auth/create/state.ts:110
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:70
|
||||
|
@ -4964,7 +4964,7 @@ msgstr ""
|
|||
|
||||
#: src/view/com/modals/ChangePassword.tsx:155
|
||||
msgid "Your password has been changed successfully!"
|
||||
msgstr ""
|
||||
msgstr "S'ha canviat la teva contrasenya!"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:274
|
||||
msgid "Your post has been published"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -8,8 +8,8 @@ msgstr ""
|
|||
"Language: pt-BR\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-02-08 19:59\n"
|
||||
"Last-Translator: maisondasilva\n"
|
||||
"PO-Revision-Date: 2024-03-12 11:36\n"
|
||||
"Last-Translator: gildaswise\n"
|
||||
"Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
|
@ -17,28 +17,10 @@ msgstr ""
|
|||
msgid "(no email)"
|
||||
msgstr "(sem email)"
|
||||
|
||||
#: src/view/shell/desktop/RightNav.tsx:168
|
||||
#~ 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/profile/ProfileHeader.tsx:593
|
||||
msgid "{following} following"
|
||||
msgstr "{following} seguindo"
|
||||
|
||||
#: src/view/shell/desktop/RightNav.tsx:151
|
||||
#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}"
|
||||
#~ msgstr "{invitesAvailable, plural, one {Convites: # disponível} other {Convites: # disponíveis}}"
|
||||
|
||||
#: src/view/screens/Settings.tsx:435
|
||||
#: src/view/shell/Drawer.tsx:664
|
||||
#~ msgid "{invitesAvailable} invite code available"
|
||||
#~ msgstr "{invitesAvailable} convite disponível"
|
||||
|
||||
#: src/view/screens/Settings.tsx:437
|
||||
#: src/view/shell/Drawer.tsx:666
|
||||
#~ msgid "{invitesAvailable} invite codes available"
|
||||
#~ msgstr "{invitesAvailable} convites disponíveis"
|
||||
|
||||
#: src/view/shell/Drawer.tsx:440
|
||||
msgid "{numUnreadNotifications} unread"
|
||||
msgstr "{numUnreadNotifications} não lidas"
|
||||
|
@ -179,11 +161,11 @@ msgstr "Adicionar prévia de link:"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:158
|
||||
msgid "Add mute word for configured settings"
|
||||
msgstr ""
|
||||
msgstr "Adicionar palavra silenciada para as configurações selecionadas"
|
||||
|
||||
#: src/components/dialogs/MutedWords.tsx:87
|
||||
msgid "Add muted words and tags"
|
||||
msgstr ""
|
||||
msgstr "Adicionar palavras/tags silenciadas"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:417
|
||||
msgid "Add the following DNS record to your domain:"
|
||||
|
@ -223,17 +205,13 @@ msgstr "Conteúdo Adulto"
|
|||
msgid "Adult content can only be enabled via the Web at <0/>."
|
||||
msgstr "Conteúdo adulto só pode ser habilitado no site: <0/>."
|
||||
|
||||
#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78
|
||||
#~ msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>."
|
||||
#~ msgstr "Conteúdo adulto só pode ser habilitado no site: <0>bsky.app</0>."
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:664
|
||||
msgid "Advanced"
|
||||
msgstr "Avançado"
|
||||
|
||||
#: src/view/screens/Feeds.tsx:666
|
||||
msgid "All the feeds you've saved, right in one place."
|
||||
msgstr ""
|
||||
msgstr "Todos os feeds que você salvou, em um único lugar."
|
||||
|
||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:221
|
||||
#: src/view/com/modals/ChangePassword.tsx:168
|
||||
|
@ -458,7 +436,7 @@ msgstr "Bluesky"
|
|||
|
||||
#: src/view/com/auth/server-input/index.tsx:150
|
||||
msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers."
|
||||
msgstr ""
|
||||
msgstr "Bluesky é uma rede aberta que permite a escolha do seu provedor de hospedagem. Desenvolvedores já conseguem utilizar a versão beta de hospedagem própria."
|
||||
|
||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80
|
||||
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:80
|
||||
|
@ -483,10 +461,6 @@ msgstr "Bluesky é público."
|
|||
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."
|
||||
msgstr "O Bluesky não mostrará seu perfil e publicações para usuários desconectados. Outros aplicativos podem não honrar esta solicitação. Isso não torna a sua conta privada."
|
||||
|
||||
#: src/view/com/modals/ServerInput.tsx:78
|
||||
#~ msgid "Bluesky.Social"
|
||||
#~ msgstr "Bluesky.Social"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:33
|
||||
msgid "Books"
|
||||
msgstr "Livros"
|
||||
|
@ -500,10 +474,6 @@ msgstr "Versão {0} {1}"
|
|||
msgid "Business"
|
||||
msgstr "Empresarial"
|
||||
|
||||
#: src/view/com/modals/ServerInput.tsx:115
|
||||
#~ msgid "Button disabled. Input custom domain to proceed."
|
||||
#~ msgstr "Botão desabilitado. Utilize um domínio personalizado para continuar."
|
||||
|
||||
#: src/view/com/profile/ProfileSubpageHeader.tsx:157
|
||||
msgid "by —"
|
||||
msgstr "por -"
|
||||
|
@ -665,10 +635,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/screens/Onboarding/StepAlgoFeeds/index.tsx:103
|
||||
#~ msgid "Choose your algorithmic feeds"
|
||||
#~ msgstr "Escolha seus feeds algoritmicos"
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103
|
||||
msgid "Choose your main feeds"
|
||||
msgstr "Escolha seus feeds principais"
|
||||
|
@ -706,11 +672,11 @@ msgstr "clique aqui"
|
|||
|
||||
#: src/components/TagMenu/index.web.tsx:138
|
||||
msgid "Click here to open tag menu for {tag}"
|
||||
msgstr ""
|
||||
msgstr "Clique aqui para abrir o menu da tag {tag}"
|
||||
|
||||
#: src/components/RichText.tsx:191
|
||||
msgid "Click here to open tag menu for #{tag}"
|
||||
msgstr ""
|
||||
msgstr "Clique aqui para abrir o menu da tag #{tag}"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:35
|
||||
msgid "Climate"
|
||||
|
@ -748,7 +714,7 @@ msgstr "Fechar o painel de navegação"
|
|||
|
||||
#: src/components/TagMenu/index.tsx:262
|
||||
msgid "Close this dialog"
|
||||
msgstr ""
|
||||
msgstr "Fechar esta janela"
|
||||
|
||||
#: src/view/shell/index.web.tsx:52
|
||||
msgid "Closes bottom navigation bar"
|
||||
|
@ -789,7 +755,7 @@ msgstr "Completar e começar a usar sua conta"
|
|||
|
||||
#: src/view/com/auth/create/Step3.tsx:73
|
||||
msgid "Complete the challenge"
|
||||
msgstr ""
|
||||
msgstr "Complete o captcha"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:424
|
||||
msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length"
|
||||
|
@ -964,10 +930,6 @@ msgstr "Não foi possível carregar o feed"
|
|||
msgid "Could not load list"
|
||||
msgstr "Não foi possível carregar a lista"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:91
|
||||
#~ msgid "Country"
|
||||
#~ msgstr "País"
|
||||
|
||||
#: src/view/com/auth/HomeLoggedOutCTA.tsx:62
|
||||
#: src/view/com/auth/SplashScreen.tsx:71
|
||||
#: src/view/com/auth/SplashScreen.web.tsx:81
|
||||
|
@ -1014,7 +976,7 @@ msgstr "Cultura"
|
|||
#: src/view/com/auth/server-input/index.tsx:95
|
||||
#: src/view/com/auth/server-input/index.tsx:96
|
||||
msgid "Custom"
|
||||
msgstr ""
|
||||
msgstr "Customizado"
|
||||
|
||||
#: src/view/com/modals/ChangeHandle.tsx:389
|
||||
msgid "Custom domain"
|
||||
|
@ -1029,10 +991,6 @@ msgstr "Feeds customizados feitos pela comunidade te proporcionam novas experiê
|
|||
msgid "Customize media from external sites."
|
||||
msgstr "Configurar mídia de sites externos."
|
||||
|
||||
#: src/view/screens/Settings.tsx:687
|
||||
#~ msgid "Danger Zone"
|
||||
#~ msgstr "Zona Perigosa"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:485
|
||||
#: src/view/screens/Settings/index.tsx:511
|
||||
msgid "Dark"
|
||||
|
@ -1072,10 +1030,6 @@ msgstr "Excluir Lista"
|
|||
msgid "Delete my account"
|
||||
msgstr "Excluir minha conta"
|
||||
|
||||
#: src/view/screens/Settings.tsx:706
|
||||
#~ msgid "Delete my account…"
|
||||
#~ msgstr "Excluir minha conta…"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:784
|
||||
msgid "Delete My Account…"
|
||||
msgstr "Excluir minha conta…"
|
||||
|
@ -1133,13 +1087,9 @@ msgstr "Desencorajar aplicativos a mostrar minha conta para usuários deslogados
|
|||
msgid "Discover new custom feeds"
|
||||
msgstr "Descubra novos feeds"
|
||||
|
||||
#: src/view/screens/Feeds.tsx:473
|
||||
#~ msgid "Discover new feeds"
|
||||
#~ msgstr "Descubra novos feeds"
|
||||
|
||||
#: src/view/screens/Feeds.tsx:689
|
||||
msgid "Discover New Feeds"
|
||||
msgstr ""
|
||||
msgstr "Descubra Novos Feeds"
|
||||
|
||||
#: src/view/com/modals/EditProfile.tsx:192
|
||||
msgid "Display name"
|
||||
|
@ -1196,12 +1146,12 @@ msgstr "Toque duas vezes para logar"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:755
|
||||
msgid "Download Bluesky account data (repository)"
|
||||
msgstr ""
|
||||
msgstr "Baixar os dados da minha conta Bluesky (repositório)"
|
||||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:59
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:63
|
||||
msgid "Download CAR file"
|
||||
msgstr ""
|
||||
msgstr "Baixar arquivo CAR"
|
||||
|
||||
#: src/view/com/composer/text-input/TextInput.web.tsx:249
|
||||
msgid "Drop to add images"
|
||||
|
@ -1360,7 +1310,7 @@ msgstr "Insira um nome para esta Senha de Aplicativo"
|
|||
#: src/components/dialogs/MutedWords.tsx:100
|
||||
#: src/components/dialogs/MutedWords.tsx:101
|
||||
msgid "Enter a word or tag"
|
||||
msgstr ""
|
||||
msgstr "Digite uma palavra ou tag"
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:105
|
||||
msgid "Enter Confirmation Code"
|
||||
|
@ -1399,17 +1349,13 @@ msgstr "Digite o novo e-mail acima"
|
|||
msgid "Enter your new email address below."
|
||||
msgstr "Digite seu novo endereço de e-mail abaixo."
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:188
|
||||
#~ msgid "Enter your phone number"
|
||||
#~ msgstr "Digite seu número de telefone"
|
||||
|
||||
#: src/view/com/auth/login/Login.tsx:99
|
||||
msgid "Enter your username and password"
|
||||
msgstr "Digite seu nome de usuário e senha"
|
||||
|
||||
#: src/view/com/auth/create/Step3.tsx:67
|
||||
msgid "Error receiving captcha response."
|
||||
msgstr ""
|
||||
msgstr "Não foi possível processar o captcha."
|
||||
|
||||
#: src/view/screens/Search/Search.tsx:110
|
||||
msgid "Error:"
|
||||
|
@ -1447,12 +1393,12 @@ msgstr "Mostrar ou esconder o post a que você está respondendo"
|
|||
|
||||
#: src/view/screens/Settings/index.tsx:753
|
||||
msgid "Export my data"
|
||||
msgstr ""
|
||||
msgstr "Exportar meus dados"
|
||||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:44
|
||||
#: src/view/screens/Settings/index.tsx:764
|
||||
msgid "Export My Data"
|
||||
msgstr ""
|
||||
msgstr "Exportar Meus Dados"
|
||||
|
||||
#: src/view/com/modals/EmbedConsent.tsx:64
|
||||
msgid "External Media"
|
||||
|
@ -1523,14 +1469,6 @@ msgstr "Comentários"
|
|||
msgid "Feeds"
|
||||
msgstr "Feeds"
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106
|
||||
#~ msgid "Feeds are created by users and can give you entirely new experiences."
|
||||
#~ msgstr "Feeds são criados por usuários e podem te dar experiências completamente únicas."
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106
|
||||
#~ msgid "Feeds are created by users and organizations. They offer you varied experiences and suggest content you may like using algorithms."
|
||||
#~ msgstr "Feeds são criados por usuários ou organizações. Eles oferecem experiências únicas e podem te sugerir conteúdo usando algoritmos próprios."
|
||||
|
||||
#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:57
|
||||
msgid "Feeds are created by users to curate content. Choose some feeds that you find interesting."
|
||||
msgstr "Os feeds são criados por usuários para curadoria de conteúdo. Escolha alguns feeds que você acha interessantes."
|
||||
|
@ -1567,11 +1505,7 @@ msgstr "Procurando contas semelhantes..."
|
|||
|
||||
#: src/view/screens/PreferencesFollowingFeed.tsx:111
|
||||
msgid "Fine-tune the content you see on your Following feed."
|
||||
msgstr ""
|
||||
|
||||
#: src/view/screens/PreferencesHomeFeed.tsx:111
|
||||
#~ msgid "Fine-tune the content you see on your home screen."
|
||||
#~ msgstr "Ajuste o conteúdo que você vê na sua tela inicial."
|
||||
msgstr "Ajuste o conteúdo que você vê na sua tela inicial."
|
||||
|
||||
#: src/view/screens/PreferencesThreads.tsx:60
|
||||
msgid "Fine-tune the discussion threads."
|
||||
|
@ -1659,7 +1593,7 @@ msgstr "Seguindo {0}"
|
|||
#: src/view/screens/PreferencesFollowingFeed.tsx:104
|
||||
#: src/view/screens/Settings/index.tsx:543
|
||||
msgid "Following Feed Preferences"
|
||||
msgstr ""
|
||||
msgstr "Configurações do feed principal"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:546
|
||||
msgid "Follows you"
|
||||
|
@ -1697,7 +1631,7 @@ msgstr "Esqueci a Senha"
|
|||
#: src/screens/Hashtag.tsx:108
|
||||
#: src/screens/Hashtag.tsx:148
|
||||
msgid "From @{sanitizedAuthor}"
|
||||
msgstr ""
|
||||
msgstr "De @{sanitizedAuthor}"
|
||||
|
||||
#: src/view/com/posts/FeedItem.tsx:189
|
||||
msgctxt "from-feed"
|
||||
|
@ -1751,15 +1685,15 @@ msgstr "Usuário"
|
|||
|
||||
#: src/Navigation.tsx:270
|
||||
msgid "Hashtag"
|
||||
msgstr ""
|
||||
msgstr "Hashtag"
|
||||
|
||||
#: src/components/RichText.tsx:188
|
||||
#~ msgid "Hashtag: {tag}"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Hashtag: {tag}"
|
||||
|
||||
#: src/components/RichText.tsx:190
|
||||
msgid "Hashtag: #{tag}"
|
||||
msgstr ""
|
||||
msgstr "Hashtag: #{tag}"
|
||||
|
||||
#: src/view/com/auth/create/CreateAccount.tsx:208
|
||||
msgid "Having trouble?"
|
||||
|
@ -1930,10 +1864,6 @@ msgstr "Insira a nova senha"
|
|||
msgid "Input password for account deletion"
|
||||
msgstr "Insira a senha para excluir a conta"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:196
|
||||
#~ msgid "Input phone number for SMS verification"
|
||||
#~ msgstr "Insira o número de telefone para verificação via SMS"
|
||||
|
||||
#: src/view/com/auth/login/LoginForm.tsx:230
|
||||
msgid "Input the password tied to {identifier}"
|
||||
msgstr "Insira a senha da conta {identifier}"
|
||||
|
@ -1942,10 +1872,6 @@ msgstr "Insira a senha da conta {identifier}"
|
|||
msgid "Input the username or email address you used at signup"
|
||||
msgstr "Insira o usuário ou e-mail que você cadastrou"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:271
|
||||
#~ msgid "Input the verification code we have texted to you"
|
||||
#~ msgstr "Insira o código de verificação que enviamos para você"
|
||||
|
||||
#: src/view/com/modals/Waitlist.tsx:90
|
||||
#~ msgid "Input your email to get on the Bluesky waitlist"
|
||||
#~ msgstr "Insira seu e-mail para entrar na lista de espera do Bluesky"
|
||||
|
@ -1966,10 +1892,6 @@ msgstr "Post inválido"
|
|||
msgid "Invalid username or password"
|
||||
msgstr "Credenciais inválidas"
|
||||
|
||||
#: src/view/screens/Settings.tsx:411
|
||||
#~ msgid "Invite"
|
||||
#~ msgstr "Convidar"
|
||||
|
||||
#: src/view/com/modals/InviteCodes.tsx:93
|
||||
msgid "Invite a Friend"
|
||||
msgstr "Convide um Amigo"
|
||||
|
@ -1987,10 +1909,6 @@ msgstr "Convite inválido. Verifique se você o inseriu corretamente e tente nov
|
|||
msgid "Invite codes: {0} available"
|
||||
msgstr "Convites: {0} disponíveis"
|
||||
|
||||
#: src/view/shell/Drawer.tsx:645
|
||||
#~ msgid "Invite codes: {invitesAvailable} available"
|
||||
#~ msgstr "Convites: {invitesAvailable} disponível"
|
||||
|
||||
#: src/view/com/modals/InviteCodes.tsx:169
|
||||
msgid "Invite codes: 1 available"
|
||||
msgstr "Convites: 1 disponível"
|
||||
|
@ -2232,15 +2150,15 @@ msgstr "Certifique-se de onde está indo!"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:83
|
||||
msgid "Manage your muted words and tags"
|
||||
msgstr ""
|
||||
msgstr "Gerencie suas palavras/tags silenciadas"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:118
|
||||
msgid "May not be longer than 253 characters"
|
||||
msgstr ""
|
||||
msgstr "Não pode ter mais que 253 caracteres"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:109
|
||||
msgid "May only contain letters and numbers"
|
||||
msgstr ""
|
||||
msgstr "Só pode conter letras e números"
|
||||
|
||||
#: src/view/screens/Profile.tsx:182
|
||||
msgid "Media"
|
||||
|
@ -2332,15 +2250,15 @@ msgstr "Respostas mais curtidas primeiro"
|
|||
|
||||
#: src/view/com/auth/create/Step2.tsx:122
|
||||
msgid "Must be at least 3 characters"
|
||||
msgstr ""
|
||||
msgstr "Deve ter no mínimo 3 caracteres"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:249
|
||||
msgid "Mute"
|
||||
msgstr ""
|
||||
msgstr "Silenciar"
|
||||
|
||||
#: src/components/TagMenu/index.web.tsx:105
|
||||
msgid "Mute {truncatedTag}"
|
||||
msgstr ""
|
||||
msgstr "Silenciar {truncatedTag}"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:327
|
||||
msgid "Mute Account"
|
||||
|
@ -2352,19 +2270,19 @@ msgstr "Silenciar contas"
|
|||
|
||||
#: src/components/TagMenu/index.tsx:209
|
||||
msgid "Mute all {displayTag} posts"
|
||||
msgstr ""
|
||||
msgstr "Silenciar posts com {displayTag}"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:211
|
||||
#~ msgid "Mute all {tag} posts"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Silenciar posts com {tag}"
|
||||
|
||||
#: src/components/dialogs/MutedWords.tsx:149
|
||||
msgid "Mute in tags only"
|
||||
msgstr ""
|
||||
msgstr "Silenciar apenas as tags"
|
||||
|
||||
#: src/components/dialogs/MutedWords.tsx:134
|
||||
msgid "Mute in text & tags"
|
||||
msgstr ""
|
||||
msgstr "Silenciar texto e tags"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:491
|
||||
msgid "Mute list"
|
||||
|
@ -2380,11 +2298,11 @@ msgstr "Silenciar esta lista"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:127
|
||||
msgid "Mute this word in post text and tags"
|
||||
msgstr ""
|
||||
msgstr "Silenciar esta palavra no conteúdo de um post e tags"
|
||||
|
||||
#: src/components/dialogs/MutedWords.tsx:142
|
||||
msgid "Mute this word in tags only"
|
||||
msgstr ""
|
||||
msgstr "Silenciar esta palavra apenas nas tags de um post"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:251
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:257
|
||||
|
@ -2394,7 +2312,7 @@ msgstr "Silenciar thread"
|
|||
#: src/view/com/util/forms/PostDropdownBtn.tsx:267
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:269
|
||||
msgid "Mute words & tags"
|
||||
msgstr ""
|
||||
msgstr "Silenciar palavras/tags"
|
||||
|
||||
#: src/view/com/lists/ListCard.tsx:102
|
||||
msgid "Muted"
|
||||
|
@ -2415,7 +2333,7 @@ msgstr "Contas silenciadas não aparecem no seu feed ou nas suas notificações.
|
|||
|
||||
#: src/view/screens/Moderation.tsx:100
|
||||
msgid "Muted words & tags"
|
||||
msgstr ""
|
||||
msgstr "Palavras/tags silenciadas"
|
||||
|
||||
#: src/view/screens/ProfileList.tsx:277
|
||||
msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them."
|
||||
|
@ -2439,7 +2357,7 @@ msgstr "Meus Feeds Salvos"
|
|||
|
||||
#: src/view/com/auth/server-input/index.tsx:118
|
||||
msgid "my-server.com"
|
||||
msgstr ""
|
||||
msgstr "meu-servidor.com.br"
|
||||
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:179
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:290
|
||||
|
@ -2482,7 +2400,7 @@ msgstr "Nunca perca o acesso aos seus seguidores ou dados."
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:293
|
||||
msgid "Nevermind"
|
||||
msgstr ""
|
||||
msgstr "Deixa pra lá"
|
||||
|
||||
#: src/view/screens/Lists.tsx:76
|
||||
msgctxt "action"
|
||||
|
@ -2587,7 +2505,7 @@ msgstr "Nenhum resultado"
|
|||
|
||||
#: src/components/Lists.tsx:192
|
||||
msgid "No results found"
|
||||
msgstr ""
|
||||
msgstr "Nenhum resultado encontrado"
|
||||
|
||||
#: src/view/screens/Feeds.tsx:495
|
||||
msgid "No results found for \"{query}\""
|
||||
|
@ -2669,7 +2587,7 @@ msgstr "Apenas {0} pode responder."
|
|||
|
||||
#: src/components/Lists.tsx:82
|
||||
msgid "Oops, something went wrong!"
|
||||
msgstr ""
|
||||
msgstr "Opa, algo deu errado!"
|
||||
|
||||
#: src/components/Lists.tsx:188
|
||||
#: src/view/screens/AppPasswords.tsx:65
|
||||
|
@ -2683,7 +2601,7 @@ msgstr "Abrir"
|
|||
|
||||
#: src/view/screens/Moderation.tsx:75
|
||||
msgid "Open content filtering settings"
|
||||
msgstr ""
|
||||
msgstr "Abrir configurações de filtro"
|
||||
|
||||
#: src/view/com/composer/Composer.tsx:477
|
||||
#: src/view/com/composer/Composer.tsx:478
|
||||
|
@ -2696,7 +2614,7 @@ msgstr "Abrir links no navegador interno"
|
|||
|
||||
#: src/view/screens/Moderation.tsx:92
|
||||
msgid "Open muted words settings"
|
||||
msgstr ""
|
||||
msgstr "Abrir configurações das palavras silenciadas"
|
||||
|
||||
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:50
|
||||
msgid "Open navigation"
|
||||
|
@ -2704,7 +2622,7 @@ msgstr "Abrir navegação"
|
|||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:175
|
||||
msgid "Open post options menu"
|
||||
msgstr ""
|
||||
msgstr "Abrir opções do post"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:804
|
||||
msgid "Open storybook page"
|
||||
|
@ -2754,10 +2672,6 @@ msgstr "Abre lista de seguidores"
|
|||
msgid "Opens following list"
|
||||
msgstr "Abre lista de seguidos"
|
||||
|
||||
#: src/view/screens/Settings.tsx:412
|
||||
#~ msgid "Opens invite code list"
|
||||
#~ msgstr "Abre lista de convites"
|
||||
|
||||
#: src/view/com/modals/InviteCodes.tsx:172
|
||||
msgid "Opens list of invite codes"
|
||||
msgstr "Abre a lista de códigos de convite"
|
||||
|
@ -2819,10 +2733,6 @@ msgstr "Ou combine estas opções:"
|
|||
msgid "Other account"
|
||||
msgstr "Outra conta"
|
||||
|
||||
#: src/view/com/modals/ServerInput.tsx:88
|
||||
#~ msgid "Other service"
|
||||
#~ msgstr "Outro serviço"
|
||||
|
||||
#: src/view/com/composer/select-language/SelectLangBtn.tsx:91
|
||||
msgid "Other..."
|
||||
msgstr "Outro..."
|
||||
|
@ -2872,10 +2782,6 @@ msgstr "A permissão de galeria foi recusada. Por favor, habilite-a nas configur
|
|||
msgid "Pets"
|
||||
msgstr "Pets"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:183
|
||||
#~ msgid "Phone number"
|
||||
#~ msgstr "Número de telefone"
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:121
|
||||
msgid "Pictures meant for adults."
|
||||
msgstr "Imagens destinadas a adultos."
|
||||
|
@ -2912,7 +2818,7 @@ msgstr "Por favor, escolha sua senha."
|
|||
|
||||
#: src/view/com/auth/create/state.ts:131
|
||||
msgid "Please complete the verification captcha."
|
||||
msgstr ""
|
||||
msgstr "Por favor, complete o captcha de verificação."
|
||||
|
||||
#: 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."
|
||||
|
@ -2922,17 +2828,13 @@ msgstr "Por favor, confirme seu e-mail antes de alterá-lo. Este é um requisito
|
|||
msgid "Please enter a name for your app password. All spaces is not allowed."
|
||||
msgstr "Por favor, insira um nome para a sua Senha de Aplicativo."
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:206
|
||||
#~ msgid "Please enter a phone number that can receive SMS text messages."
|
||||
#~ msgstr "Por favor, insira um número de telefone que possa receber mensagens SMS."
|
||||
|
||||
#: src/view/com/modals/AddAppPasswords.tsx:145
|
||||
msgid "Please enter a unique name for this App Password or use our randomly generated one."
|
||||
msgstr "Por favor, insira um nome único para esta Senha de Aplicativo ou use nosso nome gerado automaticamente."
|
||||
|
||||
#: src/components/dialogs/MutedWords.tsx:68
|
||||
msgid "Please enter a valid word, tag, or phrase to mute"
|
||||
msgstr ""
|
||||
msgstr "Por favor, insira uma palavra, tag ou frase para silenciar"
|
||||
|
||||
#: src/view/com/auth/create/state.ts:170
|
||||
#~ msgid "Please enter the code you received by SMS."
|
||||
|
@ -2955,11 +2857,6 @@ msgstr "Por favor, digite sua senha também:"
|
|||
msgid "Please tell us why you think this content warning was incorrectly applied!"
|
||||
msgstr "Por favor, diga-nos por que você acha que este aviso de conteúdo foi aplicado incorretamente!"
|
||||
|
||||
#: src/view/com/modals/AppealLabel.tsx:72
|
||||
#: src/view/com/modals/AppealLabel.tsx:75
|
||||
#~ msgid "Please tell us why you think this decision was incorrect."
|
||||
#~ msgstr "Por favor, conte-nos por que achou que esta decisão está incorreta."
|
||||
|
||||
#: src/view/com/modals/VerifyEmail.tsx:101
|
||||
msgid "Please Verify Your Email"
|
||||
msgstr "Por favor, verifique seu e-mail"
|
||||
|
@ -3019,7 +2916,7 @@ msgstr "Post não encontrado"
|
|||
|
||||
#: src/components/TagMenu/index.tsx:253
|
||||
msgid "posts"
|
||||
msgstr ""
|
||||
msgstr "posts"
|
||||
|
||||
#: src/view/screens/Profile.tsx:180
|
||||
msgid "Posts"
|
||||
|
@ -3027,7 +2924,7 @@ msgstr "Posts"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:90
|
||||
msgid "Posts can be muted based on their text, their tags, or both."
|
||||
msgstr ""
|
||||
msgstr "Posts podem ser silenciados baseados no seu conteúdo, tags ou ambos."
|
||||
|
||||
#: src/view/com/posts/FeedErrorMessage.tsx:64
|
||||
msgid "Posts hidden"
|
||||
|
@ -3171,7 +3068,7 @@ msgstr "Remover visualização da imagem"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:343
|
||||
msgid "Remove mute word from your list"
|
||||
msgstr ""
|
||||
msgstr "Remover palavra silenciada da lista"
|
||||
|
||||
#: src/view/com/modals/Repost.tsx:47
|
||||
msgid "Remove repost"
|
||||
|
@ -3286,10 +3183,6 @@ msgstr "Reposts"
|
|||
msgid "Request Change"
|
||||
msgstr "Solicitar Alteração"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:219
|
||||
#~ msgid "Request code"
|
||||
#~ msgstr "Solicitar código"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:239
|
||||
#: src/view/com/modals/ChangePassword.tsx:241
|
||||
msgid "Request Code"
|
||||
|
@ -3368,10 +3261,6 @@ msgstr "Tente novamente"
|
|||
msgid "Return to previous page"
|
||||
msgstr "Voltar para página anterior"
|
||||
|
||||
#: src/view/shell/desktop/RightNav.tsx:55
|
||||
#~ msgid "SANDBOX. Posts and accounts are not permanent."
|
||||
#~ msgstr "SANDBOX. Posts e contas não são permanentes."
|
||||
|
||||
#: src/view/com/lightbox/Lightbox.tsx:132
|
||||
#: src/view/com/modals/CreateOrEditList.tsx:345
|
||||
msgctxt "action"
|
||||
|
@ -3447,19 +3336,19 @@ msgstr "Pesquisar por \"{query}\""
|
|||
|
||||
#: src/components/TagMenu/index.tsx:145
|
||||
msgid "Search for all posts by @{authorHandle} with tag {displayTag}"
|
||||
msgstr ""
|
||||
msgstr "Pesquisar por posts de @{authorHandle} com a tag {displayTag}"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:145
|
||||
#~ msgid "Search for all posts by @{authorHandle} with tag {tag}"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Pesquisar por posts de @{authorHandle} com a tag {tag}"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:94
|
||||
msgid "Search for all posts with tag {displayTag}"
|
||||
msgstr ""
|
||||
msgstr "Pesquisar por posts com a tag {displayTag}"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:90
|
||||
#~ msgid "Search for all posts with tag {tag}"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Pesquisar por posts com a tag {tag}"
|
||||
|
||||
#: src/view/com/auth/LoggedOut.tsx:104
|
||||
#: src/view/com/auth/LoggedOut.tsx:105
|
||||
|
@ -3473,27 +3362,27 @@ msgstr "Passo de Segurança Necessário"
|
|||
|
||||
#: src/components/TagMenu/index.web.tsx:66
|
||||
msgid "See {truncatedTag} posts"
|
||||
msgstr ""
|
||||
msgstr "Ver posts com {truncatedTag}"
|
||||
|
||||
#: src/components/TagMenu/index.web.tsx:83
|
||||
msgid "See {truncatedTag} posts by user"
|
||||
msgstr ""
|
||||
msgstr "Ver posts com {truncatedTag} deste usuário"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:128
|
||||
msgid "See <0>{displayTag}</0> posts"
|
||||
msgstr ""
|
||||
msgstr "Ver posts com <0>{displayTag}</0>"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:187
|
||||
msgid "See <0>{displayTag}</0> posts by this user"
|
||||
msgstr ""
|
||||
msgstr "Ver posts com <0>{displayTag}</0> deste usuário"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:128
|
||||
#~ msgid "See <0>{tag}</0> posts"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Ver posts com <0>{tag}</0>"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:189
|
||||
#~ msgid "See <0>{tag}</0> posts by this user"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Ver posts com <0>{tag}</0> deste usuário"
|
||||
|
||||
#: src/view/screens/SavedFeeds.tsx:163
|
||||
msgid "See this guide"
|
||||
|
@ -3507,10 +3396,6 @@ msgstr "Veja o que vem por aí"
|
|||
msgid "Select {item}"
|
||||
msgstr "Selecionar {item}"
|
||||
|
||||
#: src/view/com/modals/ServerInput.tsx:75
|
||||
#~ msgid "Select Bluesky Social"
|
||||
#~ msgstr "Selecionar Bluesky Social"
|
||||
|
||||
#: src/view/com/auth/login/Login.tsx:117
|
||||
msgid "Select from an existing account"
|
||||
msgstr "Selecionar de uma conta existente"
|
||||
|
@ -3530,11 +3415,7 @@ msgstr "Selecione algumas contas para seguir"
|
|||
|
||||
#: src/view/com/auth/server-input/index.tsx:82
|
||||
msgid "Select the service that hosts your data."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Onboarding/StepModeration/index.tsx:49
|
||||
#~ msgid "Select the types of content that you want to see (or not see), and we'll handle the rest."
|
||||
#~ msgstr "Selecione os tipos de conteúdo que você quer (ou não) ver, e cuidaremos do resto."
|
||||
msgstr "Selecione o serviço que hospeda seus dados."
|
||||
|
||||
#: src/screens/Onboarding/StepTopicalFeeds.tsx:96
|
||||
msgid "Select topical feeds to follow from the list below"
|
||||
|
@ -3556,10 +3437,6 @@ msgstr "Selecione o idioma do seu aplicativo"
|
|||
msgid "Select your interests from the options below"
|
||||
msgstr "Selecione seus interesses"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:155
|
||||
#~ msgid "Select your phone's country"
|
||||
#~ msgstr "Selecione o país do número de telefone"
|
||||
|
||||
#: src/view/screens/LanguageSettings.tsx:190
|
||||
msgid "Select your preferred language for translations in your feed."
|
||||
msgstr "Selecione seu idioma preferido para as traduções no seu feed."
|
||||
|
@ -3601,7 +3478,7 @@ msgstr "Envia o e-mail com o código de confirmação para excluir a conta"
|
|||
|
||||
#: src/view/com/auth/server-input/index.tsx:110
|
||||
msgid "Server address"
|
||||
msgstr ""
|
||||
msgstr "URL do servidor"
|
||||
|
||||
#: src/view/com/modals/ContentFilteringSettings.tsx:311
|
||||
msgid "Set {value} for {labelGroup} content moderation policy"
|
||||
|
@ -3657,13 +3534,9 @@ msgstr "Defina esta configuração como \"Não\" para ocultar todos os reposts d
|
|||
msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature."
|
||||
msgstr "Defina esta configuração como \"Sim\" para mostrar respostas em uma visualização de thread. Este é um recurso experimental."
|
||||
|
||||
#: 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 "Defina esta configuração como \"Sim\" para mostrar amostras de seus feeds salvos na sua página inicial. Este é um recurso experimental."
|
||||
|
||||
#: src/view/screens/PreferencesFollowingFeed.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 "Defina esta configuração como \"Sim\" para exibir amostras de seus feeds salvos no seu feed inicial. Este é um recurso experimental."
|
||||
|
||||
#: src/screens/Onboarding/Layout.tsx:50
|
||||
msgid "Set up your account"
|
||||
|
@ -3893,10 +3766,6 @@ msgstr "Pular"
|
|||
msgid "Skip this flow"
|
||||
msgstr "Pular"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:82
|
||||
#~ msgid "SMS verification"
|
||||
#~ msgstr "Verificação por SMS"
|
||||
|
||||
#: src/screens/Onboarding/index.tsx:40
|
||||
msgid "Software Dev"
|
||||
msgstr "Desenvolvimento de software"
|
||||
|
@ -3907,7 +3776,7 @@ msgstr "Desenvolvimento de software"
|
|||
|
||||
#: src/components/Lists.tsx:203
|
||||
msgid "Something went wrong!"
|
||||
msgstr ""
|
||||
msgstr "Algo deu errado!"
|
||||
|
||||
#: src/view/com/modals/Waitlist.tsx:51
|
||||
#~ msgid "Something went wrong. Check your email and try again."
|
||||
|
@ -3933,10 +3802,6 @@ msgstr "Esportes"
|
|||
msgid "Square"
|
||||
msgstr "Quadrado"
|
||||
|
||||
#: src/view/com/modals/ServerInput.tsx:62
|
||||
#~ msgid "Staging"
|
||||
#~ msgstr "Staging"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:871
|
||||
msgid "Status page"
|
||||
msgstr "Página de status"
|
||||
|
@ -3989,10 +3854,6 @@ msgstr "Sugestivo"
|
|||
msgid "Support"
|
||||
msgstr "Suporte"
|
||||
|
||||
#: src/view/com/modals/ProfilePreview.tsx:110
|
||||
#~ msgid "Swipe up to see more"
|
||||
#~ msgstr "Deslize para cima para ver mais"
|
||||
|
||||
#: src/view/com/modals/SwitchAccount.tsx:117
|
||||
msgid "Switch Account"
|
||||
msgstr "Alterar Conta"
|
||||
|
@ -4017,15 +3878,15 @@ msgstr "Log do sistema"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:337
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
msgstr "tag"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:78
|
||||
msgid "Tag menu: {displayTag}"
|
||||
msgstr ""
|
||||
msgstr "Menu da tag: {displayTag}"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:74
|
||||
#~ msgid "Tag menu: {tag}"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Menu da tag: {tag}"
|
||||
|
||||
#: src/view/com/modals/crop-image/CropImage.web.tsx:112
|
||||
msgid "Tall"
|
||||
|
@ -4052,7 +3913,7 @@ msgstr "Termos de Serviço"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:337
|
||||
msgid "text"
|
||||
msgstr ""
|
||||
msgstr "texto"
|
||||
|
||||
#: src/view/com/modals/AppealLabel.tsx:70
|
||||
#: src/view/com/modals/report/InputIssueDetails.tsx:51
|
||||
|
@ -4061,7 +3922,7 @@ msgstr "Campo de entrada de texto"
|
|||
|
||||
#: src/view/com/auth/create/CreateAccount.tsx:94
|
||||
msgid "That handle is already taken."
|
||||
msgstr ""
|
||||
msgstr "Este identificador de usuário já está sendo usado."
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:263
|
||||
msgid "The account will be able to interact with you after unblocking."
|
||||
|
@ -4179,10 +4040,6 @@ msgstr "Houve um problema inesperado no aplicativo. Por favor, deixe-nos saber s
|
|||
msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can."
|
||||
msgstr "Muitos usuários estão tentando acessar o Bluesky! Ativaremos sua conta assim que possível."
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:55
|
||||
#~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!"
|
||||
#~ msgstr "Houve um problema com este número. Por favor, escolha um país e digite seu número de telefone completo!"
|
||||
|
||||
#: 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:"
|
||||
|
@ -4209,7 +4066,7 @@ msgstr "Este conteúdo não é visível sem uma conta do Bluesky."
|
|||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:75
|
||||
msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>"
|
||||
msgstr ""
|
||||
msgstr "Esta funcionalidade está em beta. Você pode ler mais sobre exportação de repositórios <0>neste post</0> do nosso blog."
|
||||
|
||||
#: src/view/com/posts/FeedErrorMessage.tsx:114
|
||||
msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later."
|
||||
|
@ -4261,17 +4118,13 @@ msgstr "Este usuário está incluído na lista <0/>, que você bloqueou."
|
|||
msgid "This user is included in the <0/> list which you have muted."
|
||||
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."
|
||||
#~ msgstr "Este usuário está incluído na lista <0/>, que você silenciou."
|
||||
|
||||
#: src/view/com/modals/SelfLabel.tsx:137
|
||||
msgid "This warning is only available for posts with media attached."
|
||||
msgstr "Este aviso só está disponível para publicações com mídia anexada."
|
||||
|
||||
#: src/components/dialogs/MutedWords.tsx:285
|
||||
msgid "This will delete {0} from your muted words. You can always add it back later."
|
||||
msgstr ""
|
||||
msgstr "Isso removerá {0} das suas palavras silenciadas. Você pode adicioná-la novamente depois."
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:282
|
||||
msgid "This will hide this post from your feeds."
|
||||
|
@ -4292,7 +4145,7 @@ msgstr "Preferências das Threads"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:113
|
||||
msgid "Toggle between muted word options."
|
||||
msgstr ""
|
||||
msgstr "Alternar entre opções de uma palavra silenciada"
|
||||
|
||||
#: src/view/com/util/forms/DropdownButton.tsx:246
|
||||
msgid "Toggle dropdown"
|
||||
|
@ -4376,7 +4229,7 @@ msgstr "Dessilenciar"
|
|||
|
||||
#: src/components/TagMenu/index.web.tsx:104
|
||||
msgid "Unmute {truncatedTag}"
|
||||
msgstr ""
|
||||
msgstr "Dessilenciar {truncatedTag}"
|
||||
|
||||
#: src/view/com/profile/ProfileHeader.tsx:326
|
||||
msgid "Unmute Account"
|
||||
|
@ -4384,11 +4237,11 @@ msgstr "Dessilenciar conta"
|
|||
|
||||
#: src/components/TagMenu/index.tsx:208
|
||||
msgid "Unmute all {displayTag} posts"
|
||||
msgstr ""
|
||||
msgstr "Dessilenciar posts com {displayTag}"
|
||||
|
||||
#: src/components/TagMenu/index.tsx:210
|
||||
#~ msgid "Unmute all {tag} posts"
|
||||
#~ msgstr ""
|
||||
#~ msgstr "Dessilenciar posts com {tag}"
|
||||
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:251
|
||||
#: src/view/com/util/forms/PostDropdownBtn.tsx:256
|
||||
|
@ -4446,10 +4299,6 @@ msgstr "Usar o meu navegador padrão"
|
|||
msgid "Use this to sign into the other app along with your handle."
|
||||
msgstr "Use esta senha para entrar no outro aplicativo juntamente com seu identificador."
|
||||
|
||||
#: src/view/com/modals/ServerInput.tsx:105
|
||||
#~ msgid "Use your domain as your Bluesky client service provider"
|
||||
#~ msgstr "Use seu domínio como o provedor de serviço do Bluesky"
|
||||
|
||||
#: src/view/com/modals/InviteCodes.tsx:200
|
||||
msgid "Used by:"
|
||||
msgstr "Usado por:"
|
||||
|
@ -4514,10 +4363,6 @@ msgstr "usuários seguidos por <0/>"
|
|||
msgid "Users in \"{0}\""
|
||||
msgstr "Usuários em \"{0}\""
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:243
|
||||
#~ msgid "Verification code"
|
||||
#~ msgstr "Código de verificação"
|
||||
|
||||
#: src/view/screens/Settings/index.tsx:910
|
||||
msgid "Verify email"
|
||||
msgstr "Verificar e-mail"
|
||||
|
@ -4578,7 +4423,7 @@ msgstr "Também recomendamos o \"For You\", do Skygaze:"
|
|||
|
||||
#: src/screens/Hashtag.tsx:132
|
||||
msgid "We couldn't find any results for that hashtag."
|
||||
msgstr ""
|
||||
msgstr "Não encontramos nenhum post com esta hashtag."
|
||||
|
||||
#: src/screens/Deactivated.tsx:133
|
||||
msgid "We estimate {estimatedTime} until your account is ready."
|
||||
|
@ -4598,7 +4443,7 @@ msgstr "Não temos mais posts de quem você segue. Aqui estão os mais novos de
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:204
|
||||
msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown."
|
||||
msgstr ""
|
||||
msgstr "Não recomendamos utilizar palavras comuns que aparecem em muitos posts, já que isso pode resultar em filtrar todos eles."
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124
|
||||
msgid "We recommend our \"Discover\" feed:"
|
||||
|
@ -4630,7 +4475,7 @@ msgstr "Tivemos um problema ao exibir esta lista. Se continuar acontecendo, cont
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:230
|
||||
msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Não foi possível carregar sua lista de palavras silenciadas. Por favor, tente novamente."
|
||||
|
||||
#: src/view/screens/Search/Search.tsx:254
|
||||
msgid "We're sorry, but your search could not be completed. Please try again in a few minutes."
|
||||
|
@ -4688,10 +4533,6 @@ msgstr "Escreva sua resposta"
|
|||
msgid "Writers"
|
||||
msgstr "Escritores"
|
||||
|
||||
#: src/view/com/auth/create/Step2.tsx:263
|
||||
#~ msgid "XXXXXX"
|
||||
#~ msgstr "XXXXXX"
|
||||
|
||||
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:77
|
||||
#: src/view/screens/PreferencesFollowingFeed.tsx:129
|
||||
#: src/view/screens/PreferencesFollowingFeed.tsx:201
|
||||
|
@ -4702,10 +4543,6 @@ msgstr "Escritores"
|
|||
msgid "Yes"
|
||||
msgstr "Sim"
|
||||
|
||||
#: src/screens/Onboarding/StepModeration/index.tsx:46
|
||||
#~ msgid "You are in control"
|
||||
#~ msgstr "Você está no controle"
|
||||
|
||||
#: src/screens/Deactivated.tsx:130
|
||||
msgid "You are in line."
|
||||
msgstr "Você está na fila."
|
||||
|
@ -4715,10 +4552,6 @@ msgstr "Você está na fila."
|
|||
msgid "You can also discover new Custom Feeds to follow."
|
||||
msgstr "Você também pode descobrir novos feeds para seguir."
|
||||
|
||||
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:123
|
||||
#~ msgid "You can also try our \"Discover\" algorithm:"
|
||||
#~ msgstr "Você também pode tentar nosso algoritmo \"Discover\":"
|
||||
|
||||
#: src/screens/Onboarding/StepFollowingFeed.tsx:142
|
||||
msgid "You can change these settings later."
|
||||
msgstr "Você pode mudar estas configurações depois."
|
||||
|
@ -4786,7 +4619,7 @@ msgstr "Você ainda não silenciou nenhuma conta. Para silenciar uma conta, aces
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:250
|
||||
msgid "You haven't muted any words or tags yet"
|
||||
msgstr ""
|
||||
msgstr "Você não silenciou nenhuma palavra ou tag ainda"
|
||||
|
||||
#: src/view/com/modals/ContentFilteringSettings.tsx:175
|
||||
msgid "You must be 18 or older to enable adult content."
|
||||
|
@ -4836,7 +4669,7 @@ msgstr "Sua conta foi excluída"
|
|||
|
||||
#: src/view/screens/Settings/ExportCarDialog.tsx:47
|
||||
msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately."
|
||||
msgstr ""
|
||||
msgstr "O repositório da sua conta, contendo todos os seus dados públicos, pode ser baixado como um arquivo \"CAR\". Este arquivo não inclui imagens ou dados privados, estes devem ser exportados separadamente."
|
||||
|
||||
#: src/view/com/auth/create/Step1.tsx:215
|
||||
msgid "Your birth date"
|
||||
|
@ -4888,7 +4721,7 @@ msgstr "Seu usuário completo será <0>@{0}</0>"
|
|||
|
||||
#: src/components/dialogs/MutedWords.tsx:221
|
||||
msgid "Your muted words"
|
||||
msgstr ""
|
||||
msgstr "Suas palavras silenciadas"
|
||||
|
||||
#: src/view/com/modals/ChangePassword.tsx:155
|
||||
msgid "Your password has been changed successfully!"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -128,8 +128,8 @@ export default function HashtagScreen({
|
|||
isError={isError}
|
||||
isEmpty={posts.length < 1}
|
||||
onRetry={refetch}
|
||||
notFoundType="results"
|
||||
empty={_(msg`We couldn't find any results for that hashtag.`)}
|
||||
emptyTitle="results"
|
||||
emptyMessage={_(msg`We couldn't find any results for that hashtag.`)}
|
||||
/>
|
||||
{!isLoading && posts.length > 0 && (
|
||||
<List<PostView>
|
||||
|
|
|
@ -205,19 +205,111 @@ export function ModerationScreenInner({
|
|||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
a.border_0,
|
||||
a.pt_2xl,
|
||||
a.px_lg,
|
||||
gtMobile && a.px_2xl,
|
||||
]}>
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
|
||||
<Trans>Moderation tools</Trans>
|
||||
</Text>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
a.border_0,
|
||||
a.pt_2xl,
|
||||
a.px_lg,
|
||||
gtMobile && a.px_2xl,
|
||||
]}>
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
|
||||
<Trans>Moderation tools</Trans>
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Button
|
||||
testID="mutedWordsBtn"
|
||||
label={_(msg`Open muted words and tags settings`)}
|
||||
onPress={() => mutedWordsDialogControl.open()}>
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Muted words & tags`)}
|
||||
icon={Filter}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
<Divider />
|
||||
<Link testID="moderationlistsBtn" to="/moderation/modlists">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Moderation lists`)}
|
||||
icon={Group}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link testID="mutedAccountsBtn" to="/moderation/muted-accounts">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Muted accounts`)}
|
||||
icon={Person}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link testID="blockedAccountsBtn" to="/moderation/blocked-accounts">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Blocked accounts`)}
|
||||
icon={CircleBanSign}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Content filters</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={[a.gap_md]}>
|
||||
{ageNotSet && (
|
||||
<>
|
||||
<Button
|
||||
label={_(msg`Confirm your birthdate`)}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
onPress={() => {
|
||||
birthdateDialogControl.open()
|
||||
}}
|
||||
style={[a.justify_between, a.rounded_md, a.px_lg, a.py_lg]}>
|
||||
<ButtonText>
|
||||
<Trans>Confirm your age:</Trans>
|
||||
</ButtonText>
|
||||
<ButtonText>
|
||||
<Trans>Set birthdate</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<BirthDateSettingsDialog control={birthdateDialogControl} />
|
||||
</>
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
|
@ -225,234 +317,137 @@ export function ModerationScreenInner({
|
|||
a.overflow_hidden,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Button
|
||||
testID="mutedWordsBtn"
|
||||
label={_(msg`Open muted words and tags settings`)}
|
||||
onPress={() => mutedWordsDialogControl.open()}>
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Muted words & tags`)}
|
||||
icon={Filter}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
<Divider />
|
||||
<Link testID="moderationlistsBtn" to="/moderation/modlists">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Moderation lists`)}
|
||||
icon={Group}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link testID="mutedAccountsBtn" to="/moderation/muted-accounts">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Muted accounts`)}
|
||||
icon={Person}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link testID="blockedAccountsBtn" to="/moderation/blocked-accounts">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Blocked accounts`)}
|
||||
icon={CircleBanSign}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Content filters</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={[a.gap_md]}>
|
||||
{ageNotSet && (
|
||||
{!ageNotSet && !isUnderage && (
|
||||
<>
|
||||
<Button
|
||||
label={_(msg`Confirm your birthdate`)}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
onPress={() => {
|
||||
birthdateDialogControl.open()
|
||||
}}
|
||||
style={[a.justify_between, a.rounded_md, a.px_lg, a.py_lg]}>
|
||||
<ButtonText>
|
||||
<Trans>Confirm your age:</Trans>
|
||||
</ButtonText>
|
||||
<ButtonText>
|
||||
<Trans>Set birthdate</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<BirthDateSettingsDialog
|
||||
control={birthdateDialogControl}
|
||||
preferences={preferences}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.py_lg,
|
||||
a.px_lg,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
]}>
|
||||
<Text style={[a.font_semibold, t.atoms.text_contrast_high]}>
|
||||
<Trans>Enable adult content</Trans>
|
||||
</Text>
|
||||
<Toggle.Item
|
||||
label={_(msg`Toggle to enable or disable adult content`)}
|
||||
name="adultContent"
|
||||
value={adultContentEnabled}
|
||||
onChange={onToggleAdultContentEnabled}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{adultContentEnabled ? (
|
||||
<Trans>Enabled</Trans>
|
||||
) : (
|
||||
<Trans>Disabled</Trans>
|
||||
)}
|
||||
</Text>
|
||||
<Toggle.Switch />
|
||||
</View>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
{!ageNotSet && !isUnderage && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.py_lg,
|
||||
a.px_lg,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
]}>
|
||||
<Text style={[a.font_semibold, t.atoms.text_contrast_high]}>
|
||||
<Trans>Enable adult content</Trans>
|
||||
</Text>
|
||||
<Toggle.Item
|
||||
label={_(msg`Toggle to enable or disable adult content`)}
|
||||
name="adultContent"
|
||||
value={adultContentEnabled}
|
||||
onChange={onToggleAdultContentEnabled}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{adultContentEnabled ? (
|
||||
<Trans>Enabled</Trans>
|
||||
) : (
|
||||
<Trans>Disabled</Trans>
|
||||
)}
|
||||
</Text>
|
||||
<Toggle.Switch />
|
||||
</View>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
{!isUnderage && adultContentEnabled && (
|
||||
<>
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.porn} />
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref
|
||||
labelValueDefinition={LABELS.sexual}
|
||||
/>
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref
|
||||
labelValueDefinition={LABELS['graphic-media']}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.nudity} />
|
||||
</View>
|
||||
{!isUnderage && adultContentEnabled && (
|
||||
<>
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.porn} />
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.sexual} />
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref
|
||||
labelValueDefinition={LABELS['graphic-media']}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.nudity} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Advanced</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Advanced</Trans>
|
||||
</Text>
|
||||
|
||||
{isLabelersLoading ? (
|
||||
<Loader />
|
||||
) : labelersError || !labelers ? (
|
||||
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
|
||||
<Text>
|
||||
<Trans>
|
||||
We were unable to load your configured labelers at this time.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[a.rounded_sm, t.atoms.bg_contrast_25]}>
|
||||
{labelers.map((labeler, i) => {
|
||||
return (
|
||||
<React.Fragment key={labeler.creator.did}>
|
||||
{i !== 0 && <Divider />}
|
||||
<LabelingService.Link labeler={labeler}>
|
||||
{state => (
|
||||
<LabelingService.Outer
|
||||
style={[
|
||||
i === 0 && {
|
||||
borderTopLeftRadius: a.rounded_sm.borderRadius,
|
||||
borderTopRightRadius: a.rounded_sm.borderRadius,
|
||||
},
|
||||
i === labelers.length - 1 && {
|
||||
borderBottomLeftRadius: a.rounded_sm.borderRadius,
|
||||
borderBottomRightRadius: a.rounded_sm.borderRadius,
|
||||
},
|
||||
(state.hovered || state.pressed) && [
|
||||
t.atoms.bg_contrast_50,
|
||||
],
|
||||
]}>
|
||||
<LabelingService.Avatar />
|
||||
<LabelingService.Content>
|
||||
<LabelingService.Title
|
||||
value={getLabelingServiceTitle({
|
||||
displayName: labeler.creator.displayName,
|
||||
handle: labeler.creator.handle,
|
||||
})}
|
||||
/>
|
||||
<LabelingService.Description
|
||||
value={labeler.creator.description}
|
||||
handle={labeler.creator.handle}
|
||||
/>
|
||||
</LabelingService.Content>
|
||||
</LabelingService.Outer>
|
||||
)}
|
||||
</LabelingService.Link>
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
{isLabelersLoading ? (
|
||||
<View style={[a.w_full, a.align_center, a.p_lg]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : labelersError || !labelers ? (
|
||||
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
|
||||
<Text>
|
||||
<Trans>
|
||||
We were unable to load your configured labelers at this time.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[a.rounded_sm, t.atoms.bg_contrast_25]}>
|
||||
{labelers.map((labeler, i) => {
|
||||
return (
|
||||
<React.Fragment key={labeler.creator.did}>
|
||||
{i !== 0 && <Divider />}
|
||||
<LabelingService.Link labeler={labeler}>
|
||||
{state => (
|
||||
<LabelingService.Outer
|
||||
style={[
|
||||
i === 0 && {
|
||||
borderTopLeftRadius: a.rounded_sm.borderRadius,
|
||||
borderTopRightRadius: a.rounded_sm.borderRadius,
|
||||
},
|
||||
i === labelers.length - 1 && {
|
||||
borderBottomLeftRadius: a.rounded_sm.borderRadius,
|
||||
borderBottomRightRadius: a.rounded_sm.borderRadius,
|
||||
},
|
||||
(state.hovered || state.pressed) && [
|
||||
t.atoms.bg_contrast_50,
|
||||
],
|
||||
]}>
|
||||
<LabelingService.Avatar avatar={labeler.creator.avatar} />
|
||||
<LabelingService.Content>
|
||||
<LabelingService.Title
|
||||
value={getLabelingServiceTitle({
|
||||
displayName: labeler.creator.displayName,
|
||||
handle: labeler.creator.handle,
|
||||
})}
|
||||
/>
|
||||
<LabelingService.Description
|
||||
value={labeler.creator.description}
|
||||
handle={labeler.creator.handle}
|
||||
/>
|
||||
</LabelingService.Content>
|
||||
</LabelingService.Outer>
|
||||
)}
|
||||
</LabelingService.Link>
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Logged-out visibility</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Logged-out visibility</Trans>
|
||||
</Text>
|
||||
|
||||
<PwiOptOut />
|
||||
<PwiOptOut />
|
||||
|
||||
<View style={{height: 200}} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
<View style={{height: 200}} />
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -520,11 +515,12 @@ function PwiOptOut() {
|
|||
value={isOptedOut}
|
||||
onChange={onToggleOptOut}
|
||||
name="logged_out_visibility"
|
||||
style={a.flex_1}
|
||||
label={_(
|
||||
msg`Discourage apps from showing my account to logged-out users`,
|
||||
)}>
|
||||
<Toggle.Switch />
|
||||
<Toggle.Label style={[a.text_md]}>
|
||||
<Toggle.Label style={[a.text_md, a.flex_1]}>
|
||||
<Trans>
|
||||
Discourage apps from showing my account to logged-out users
|
||||
</Trans>
|
||||
|
|
|
@ -90,7 +90,9 @@ export function AdultContentEnabledPref({
|
|||
a.align_center,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Text style={[a.font_bold]}>Enable Adult Content</Text>
|
||||
<Text style={[a.font_bold]}>
|
||||
<Trans>Enable Adult Content</Trans>
|
||||
</Text>
|
||||
<Toggle.Switch />
|
||||
</View>
|
||||
</Toggle.Item>
|
||||
|
@ -111,7 +113,9 @@ export function AdultContentEnabledPref({
|
|||
)}
|
||||
|
||||
<Prompt.Outer control={prompt}>
|
||||
<Prompt.Title>Adult Content</Prompt.Title>
|
||||
<Prompt.Title>
|
||||
<Trans>Adult Content</Trans>
|
||||
</Prompt.Title>
|
||||
<Prompt.Description>
|
||||
<Trans>
|
||||
Due to Apple policies, adult content can only be enabled on the web
|
||||
|
@ -119,7 +123,9 @@ export function AdultContentEnabledPref({
|
|||
</Trans>
|
||||
</Prompt.Description>
|
||||
<Prompt.Actions>
|
||||
<Prompt.Action onPress={() => prompt.close()}>OK</Prompt.Action>
|
||||
<Prompt.Action onPress={() => prompt.close()}>
|
||||
<Trans>OK</Trans>
|
||||
</Prompt.Action>
|
||||
</Prompt.Actions>
|
||||
</Prompt.Outer>
|
||||
</>
|
||||
|
|
|
@ -48,7 +48,6 @@ export const ProfileLabelsSection = React.forwardRef<
|
|||
},
|
||||
ref,
|
||||
) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {height: minHeight} = useSafeAreaFrame()
|
||||
|
||||
|
@ -66,37 +65,26 @@ export const ProfileLabelsSection = React.forwardRef<
|
|||
}))
|
||||
|
||||
return (
|
||||
<CenteredView>
|
||||
<View
|
||||
style={[
|
||||
a.border_l,
|
||||
a.border_r,
|
||||
a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
{
|
||||
minHeight,
|
||||
},
|
||||
]}>
|
||||
{isLabelerLoading ? (
|
||||
<View style={[a.w_full, a.align_center]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : labelerError || !labelerInfo ? (
|
||||
<ErrorState
|
||||
error={
|
||||
labelerError?.toString() ||
|
||||
_(msg`Something went wrong, please try again.`)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ProfileLabelsSectionInner
|
||||
moderationOpts={moderationOpts}
|
||||
labelerInfo={labelerInfo}
|
||||
scrollElRef={scrollElRef}
|
||||
headerHeight={headerHeight}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<CenteredView style={{flex: 1, minHeight}} sideBorders>
|
||||
{isLabelerLoading ? (
|
||||
<View style={[a.w_full, a.align_center]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : labelerError || !labelerInfo ? (
|
||||
<ErrorState
|
||||
error={
|
||||
labelerError?.toString() ||
|
||||
_(msg`Something went wrong, please try again.`)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ProfileLabelsSectionInner
|
||||
moderationOpts={moderationOpts}
|
||||
labelerInfo={labelerInfo}
|
||||
scrollElRef={scrollElRef}
|
||||
headerHeight={headerHeight}
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
)
|
||||
})
|
||||
|
@ -149,13 +137,7 @@ export function ProfileLabelsSectionInner({
|
|||
}}
|
||||
contentOffset={{x: 0, y: headerHeight * -1}}
|
||||
onScroll={scrollHandler}>
|
||||
<View
|
||||
style={[
|
||||
a.pt_xl,
|
||||
a.px_lg,
|
||||
isNative && a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<View style={[a.pt_xl, a.px_lg, a.border_t, t.atoms.border_contrast_low]}>
|
||||
<View>
|
||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug, a.text_sm]}>
|
||||
<Trans>
|
||||
|
|
|
@ -101,13 +101,7 @@ function computeSuggestions(
|
|||
}
|
||||
for (const item of searched) {
|
||||
if (!items.find(item2 => item2.handle === item.handle)) {
|
||||
items.push({
|
||||
did: item.did,
|
||||
handle: item.handle,
|
||||
displayName: item.displayName,
|
||||
avatar: item.avatar,
|
||||
labels: item.labels,
|
||||
})
|
||||
items.push(item)
|
||||
}
|
||||
}
|
||||
return items.filter(profile => {
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
BskyFeedViewPreference,
|
||||
ModerationOpts,
|
||||
AppBskyActorDefs,
|
||||
BSKY_LABELER_DID,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
|
@ -19,6 +20,7 @@ import {
|
|||
DEFAULT_THREAD_VIEW_PREFS,
|
||||
DEFAULT_LOGGED_OUT_PREFERENCES,
|
||||
} from '#/state/queries/preferences/const'
|
||||
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
|
||||
import {saveLabelers} from '#/state/session/agent-config'
|
||||
|
@ -95,7 +97,18 @@ export function useModerationOpts() {
|
|||
}
|
||||
return {
|
||||
userDid: currentAccount?.did,
|
||||
prefs: {...prefs.data.moderationPrefs, hiddenPosts: hiddenPosts || []},
|
||||
prefs: {
|
||||
...prefs.data.moderationPrefs,
|
||||
labelers: prefs.data.moderationPrefs.labelers.length
|
||||
? prefs.data.moderationPrefs.labelers
|
||||
: [
|
||||
{
|
||||
did: BSKY_LABELER_DID,
|
||||
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
|
||||
},
|
||||
],
|
||||
hiddenPosts: hiddenPosts || [],
|
||||
},
|
||||
labelDefs,
|
||||
}
|
||||
}, [override, currentAccount, labelDefs, prefs.data, hiddenPosts])
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
AppBskyEmbedRecord,
|
||||
AppBskyRichtextFacet,
|
||||
ModerationDecision,
|
||||
AppBskyActorDefs,
|
||||
} from '@atproto/api'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
|
||||
|
@ -10,11 +11,7 @@ export interface ComposerOptsPostRef {
|
|||
uri: string
|
||||
cid: string
|
||||
text: string
|
||||
author: {
|
||||
handle: string
|
||||
displayName?: string
|
||||
avatar?: string
|
||||
}
|
||||
author: AppBskyActorDefs.ProfileViewBasic
|
||||
embed?: AppBskyEmbedRecord.ViewRecord['embed']
|
||||
moderation?: ModerationDecision
|
||||
}
|
||||
|
|
|
@ -52,7 +52,9 @@ export function HomeLoggedOutCTA() {
|
|||
onPress={showCreateAccount}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Create new account`)}
|
||||
accessibilityHint="Opens flow to create a new Bluesky account">
|
||||
accessibilityHint={_(
|
||||
msg`Opens flow to create a new Bluesky account`,
|
||||
)}>
|
||||
<Text
|
||||
style={[
|
||||
s.white,
|
||||
|
@ -68,7 +70,9 @@ export function HomeLoggedOutCTA() {
|
|||
onPress={showSignIn}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Sign in`)}
|
||||
accessibilityHint="Opens flow to sign into your existing Bluesky account">
|
||||
accessibilityHint={_(
|
||||
msg`Opens flow to sign into your existing Bluesky account`,
|
||||
)}>
|
||||
<Text
|
||||
style={[
|
||||
pal.text,
|
||||
|
|
|
@ -66,7 +66,9 @@ export const SplashScreen = ({
|
|||
onPress={onPressCreateAccount}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Create new account`)}
|
||||
accessibilityHint="Opens flow to create a new Bluesky account">
|
||||
accessibilityHint={_(
|
||||
msg`Opens flow to create a new Bluesky account`,
|
||||
)}>
|
||||
<Text style={[s.white, styles.btnLabel]}>
|
||||
<Trans>Create a new account</Trans>
|
||||
</Text>
|
||||
|
@ -77,7 +79,9 @@ export const SplashScreen = ({
|
|||
onPress={onPressSignin}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Sign in`)}
|
||||
accessibilityHint="Opens flow to sign into your existing Bluesky account">
|
||||
accessibilityHint={_(
|
||||
msg`Opens flow to sign into your existing Bluesky account`,
|
||||
)}>
|
||||
<Text style={[pal.text, styles.btnLabel]}>
|
||||
<Trans>Sign In</Trans>
|
||||
</Text>
|
||||
|
|
|
@ -9,6 +9,8 @@ import {TextLink} from '../../util/Link'
|
|||
import {Text} from '../../util/text/Text'
|
||||
import {s, colors} from 'lib/styles'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||
|
||||
|
@ -22,6 +24,7 @@ export const Policies = ({
|
|||
under13: boolean
|
||||
}) => {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
if (!serviceDescription) {
|
||||
return <View />
|
||||
}
|
||||
|
@ -42,7 +45,9 @@ export const Policies = ({
|
|||
/>
|
||||
</View>
|
||||
<Text style={[pal.textLight, s.pl5, s.flex1]}>
|
||||
This service has not provided terms of service or a privacy policy.
|
||||
<Trans>
|
||||
This service has not provided terms of service or a privacy policy.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
|
@ -53,7 +58,7 @@ export const Policies = ({
|
|||
<TextLink
|
||||
key="tos"
|
||||
href={tos}
|
||||
text="Terms of Service"
|
||||
text={_(msg`Terms of Service`)}
|
||||
style={[pal.link, s.underline]}
|
||||
onPress={() => Linking.openURL(tos)}
|
||||
/>,
|
||||
|
@ -64,7 +69,7 @@ export const Policies = ({
|
|||
<TextLink
|
||||
key="pp"
|
||||
href={pp}
|
||||
text="Privacy Policy"
|
||||
text={_(msg`Privacy Policy`)}
|
||||
style={[pal.link, s.underline]}
|
||||
onPress={() => Linking.openURL(pp)}
|
||||
/>,
|
||||
|
@ -83,7 +88,7 @@ export const Policies = ({
|
|||
return (
|
||||
<View style={styles.policies}>
|
||||
<Text style={pal.textLight}>
|
||||
By creating an account you agree to the {els}.
|
||||
<Trans>By creating an account you agree to the {els}.</Trans>
|
||||
</Text>
|
||||
{under13 ? (
|
||||
<Text style={[pal.textLight, s.bold]}>
|
||||
|
@ -91,8 +96,10 @@ export const Policies = ({
|
|||
</Text>
|
||||
) : needsGuardian ? (
|
||||
<Text style={[pal.textLight, s.bold]}>
|
||||
If you are not yet an adult according to the laws of your country,
|
||||
your parent or legal guardian must read these Terms on your behalf.
|
||||
<Trans>
|
||||
If you are not yet an adult according to the laws of your country,
|
||||
your parent or legal guardian must read these Terms on your behalf.
|
||||
</Trans>
|
||||
</Text>
|
||||
) : undefined}
|
||||
</View>
|
||||
|
|
|
@ -11,7 +11,8 @@ import {Text} from 'view/com/util/text/Text'
|
|||
import Animated, {FadeInRight} from 'react-native-reanimated'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
|
||||
import {logger} from '#/logger'
|
||||
|
@ -70,6 +71,7 @@ function ProfileCard({
|
|||
}) {
|
||||
const {track} = useAnalytics()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const [addingMoreSuggestions, setAddingMoreSuggestions] =
|
||||
React.useState(false)
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||
|
@ -136,7 +138,7 @@ function ProfileCard({
|
|||
type={profile.viewer?.following ? 'default' : 'inverted'}
|
||||
labelStyle={styles.followButton}
|
||||
onPress={onToggleFollow}
|
||||
label={profile.viewer?.following ? 'Unfollow' : 'Follow'}
|
||||
label={profile.viewer?.following ? _(msg`Unfollow`) : _(msg`Follow`)}
|
||||
/>
|
||||
</View>
|
||||
{profile.description ? (
|
||||
|
|
|
@ -6,7 +6,8 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Button} from 'view/com/util/forms/Button'
|
||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
|
||||
type Props = {
|
||||
next: () => void
|
||||
|
@ -15,6 +16,7 @@ type Props = {
|
|||
|
||||
export function WelcomeMobile({next, skip}: Props) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<View style={[styles.container]} testID="welcomeOnboarding">
|
||||
|
@ -91,7 +93,7 @@ export function WelcomeMobile({next, skip}: Props) {
|
|||
|
||||
<Button
|
||||
onPress={next}
|
||||
label="Continue"
|
||||
label={_(msg`Continue`)}
|
||||
testID="continueBtn"
|
||||
style={[styles.buttonContainer]}
|
||||
labelStyle={styles.buttonText}
|
||||
|
|
|
@ -115,7 +115,7 @@ export function ServerInputDialog({
|
|||
testID="customServerTextInput"
|
||||
value={customAddress}
|
||||
onChangeText={setCustomAddress}
|
||||
label={_(msg`my-server.com`)}
|
||||
label="my-server.com"
|
||||
accessibilityLabelledBy="address-input-label"
|
||||
autoCapitalize="none"
|
||||
keyboardType="url"
|
||||
|
|
|
@ -415,7 +415,11 @@ export const ComposePost = observer(function ComposePost({
|
|||
styles.textInputLayout,
|
||||
isNative && styles.textInputLayoutMobile,
|
||||
]}>
|
||||
<UserAvatar avatar={currentProfile?.avatar} size={50} />
|
||||
<UserAvatar
|
||||
avatar={currentProfile?.avatar}
|
||||
size={50}
|
||||
type={currentProfile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
<TextInput
|
||||
ref={textInput}
|
||||
richtext={richtext}
|
||||
|
|
|
@ -87,6 +87,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
|||
avatar={replyTo.author.avatar}
|
||||
size={50}
|
||||
moderation={replyTo.moderation?.ui('avatar')}
|
||||
type={replyTo.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
<View style={styles.replyToPost}>
|
||||
<Text type="xl-medium" style={[pal.text]}>
|
||||
|
|
|
@ -23,7 +23,11 @@ export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
|
|||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Compose reply`)}
|
||||
accessibilityHint={_(msg`Opens composer`)}>
|
||||
<UserAvatar avatar={profile?.avatar} size={38} />
|
||||
<UserAvatar
|
||||
avatar={profile?.avatar}
|
||||
size={38}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
<Text
|
||||
type="xl"
|
||||
style={[
|
||||
|
|
|
@ -78,7 +78,11 @@ export function Autocomplete({
|
|||
accessibilityLabel={`Select ${item.handle}`}
|
||||
accessibilityHint="">
|
||||
<View style={styles.avatarAndHandle}>
|
||||
<UserAvatar avatar={item.avatar ?? null} size={24} />
|
||||
<UserAvatar
|
||||
avatar={item.avatar ?? null}
|
||||
size={24}
|
||||
type={item.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
<Text type="md-medium" style={pal.text}>
|
||||
{displayName}
|
||||
</Text>
|
||||
|
|
|
@ -175,7 +175,11 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>(
|
|||
}}
|
||||
accessibilityRole="button">
|
||||
<View style={styles.avatarAndDisplayName}>
|
||||
<UserAvatar avatar={item.avatar ?? null} size={26} />
|
||||
<UserAvatar
|
||||
avatar={item.avatar ?? null}
|
||||
size={26}
|
||||
type={item.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
<Text style={pal.text} numberOfLines={1}>
|
||||
{displayName}
|
||||
</Text>
|
||||
|
|
|
@ -78,9 +78,9 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
|
|||
|
||||
try {
|
||||
await saveImageToMediaLibrary({uri})
|
||||
Toast.show('Saved to your camera roll.')
|
||||
Toast.show(_(msg`Saved to your camera roll.`))
|
||||
} catch (e: any) {
|
||||
Toast.show(`Failed to save image: ${String(e)}`)
|
||||
Toast.show(_(msg`Failed to save image: ${String(e)}`))
|
||||
}
|
||||
},
|
||||
[permissionResponse, requestPermission, _],
|
||||
|
|
|
@ -150,7 +150,7 @@ export function Inner({
|
|||
accessibilityHint={_(msg`Exits handle change process`)}
|
||||
onAccessibilityEscape={onPressCancel}>
|
||||
<Text type="lg" style={pal.textLight}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
@ -254,7 +254,7 @@ function ProvidedHandleForm({
|
|||
<TextInput
|
||||
testID="setHandleInput"
|
||||
style={[pal.text, styles.textInput]}
|
||||
placeholder="e.g. alice"
|
||||
placeholder={_(msg`e.g. alice`)}
|
||||
placeholderTextColor={pal.colors.textLight}
|
||||
autoCapitalize="none"
|
||||
keyboardAppearance={theme.colorScheme}
|
||||
|
@ -277,8 +277,8 @@ function ProvidedHandleForm({
|
|||
<TouchableOpacity
|
||||
onPress={onToggleCustom}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint="Hosting provider"
|
||||
accessibilityLabel={_(msg`Opens modal for using custom domain`)}>
|
||||
accessibilityLabel={_(msg`Hosting provider`)}
|
||||
accessibilityHint={_(msg`Opens modal for using custom domain`)}>
|
||||
<Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
|
||||
<Trans>I have my own domain</Trans>
|
||||
</Text>
|
||||
|
@ -324,8 +324,8 @@ function CustomHandleForm({
|
|||
Clipboard.setString(
|
||||
isDNSForm ? `did=${currentAccount.did}` : currentAccount.did,
|
||||
)
|
||||
Toast.show('Copied to clipboard')
|
||||
}, [currentAccount, isDNSForm])
|
||||
Toast.show(_(msg`Copied to clipboard`))
|
||||
}, [currentAccount, isDNSForm, _])
|
||||
const onChangeHandle = React.useCallback(
|
||||
(v: string) => {
|
||||
setHandle(v)
|
||||
|
@ -378,7 +378,7 @@ function CustomHandleForm({
|
|||
<TextInput
|
||||
testID="setHandleInput"
|
||||
style={[pal.text, styles.textInput]}
|
||||
placeholder="e.g. alice.com"
|
||||
placeholder={_(msg`e.g. alice.com`)}
|
||||
placeholderTextColor={pal.colors.textLight}
|
||||
autoCapitalize="none"
|
||||
keyboardAppearance={theme.colorScheme}
|
||||
|
@ -387,7 +387,7 @@ function CustomHandleForm({
|
|||
editable={!isProcessing}
|
||||
accessibilityLabelledBy="customDomain"
|
||||
accessibilityLabel={_(msg`Custom domain`)}
|
||||
accessibilityHint="Input your preferred hosting provider"
|
||||
accessibilityHint={_(msg`Input your preferred hosting provider`)}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.spacer} />
|
||||
|
@ -395,18 +395,18 @@ function CustomHandleForm({
|
|||
<View style={[styles.selectableBtns]}>
|
||||
<SelectableBtn
|
||||
selected={isDNSForm}
|
||||
label="DNS Panel"
|
||||
label={_(msg`DNS Panel`)}
|
||||
left
|
||||
onSelect={() => setDNSForm(true)}
|
||||
accessibilityHint="Use the DNS panel"
|
||||
accessibilityHint={_(msg`Use the DNS panel`)}
|
||||
style={s.flex1}
|
||||
/>
|
||||
<SelectableBtn
|
||||
selected={!isDNSForm}
|
||||
label="No DNS Panel"
|
||||
label={_(msg`No DNS Panel`)}
|
||||
right
|
||||
onSelect={() => setDNSForm(false)}
|
||||
accessibilityHint="Use a file on your server"
|
||||
accessibilityHint={_(msg`Use a file on your server`)}
|
||||
style={s.flex1}
|
||||
/>
|
||||
</View>
|
||||
|
@ -418,7 +418,7 @@ function CustomHandleForm({
|
|||
</Text>
|
||||
<View style={[styles.dnsTable, pal.btn]}>
|
||||
<Text type="md-medium" style={[styles.dnsLabel, pal.text]}>
|
||||
Host:
|
||||
<Trans>Host:</Trans>
|
||||
</Text>
|
||||
<View style={[styles.dnsValue]}>
|
||||
<Text type="mono" style={[styles.monoText, pal.text]}>
|
||||
|
@ -426,7 +426,7 @@ function CustomHandleForm({
|
|||
</Text>
|
||||
</View>
|
||||
<Text type="md-medium" style={[styles.dnsLabel, pal.text]}>
|
||||
Type:
|
||||
<Trans>Type:</Trans>
|
||||
</Text>
|
||||
<View style={[styles.dnsValue]}>
|
||||
<Text type="mono" style={[styles.monoText, pal.text]}>
|
||||
|
@ -434,7 +434,7 @@ function CustomHandleForm({
|
|||
</Text>
|
||||
</View>
|
||||
<Text type="md-medium" style={[styles.dnsLabel, pal.text]}>
|
||||
Value:
|
||||
<Trans>Value:</Trans>
|
||||
</Text>
|
||||
<View style={[styles.dnsValue]}>
|
||||
<Text type="mono" style={[styles.monoText, pal.text]}>
|
||||
|
@ -443,7 +443,7 @@ function CustomHandleForm({
|
|||
</View>
|
||||
</View>
|
||||
<Text type="md" style={[pal.text, s.pt20, s.pl5]}>
|
||||
This should create a domain record at:{' '}
|
||||
<Trans>This should create a domain record at:</Trans>
|
||||
</Text>
|
||||
<Text type="mono" style={[styles.monoText, pal.text, s.pt5, s.pl5]}>
|
||||
_atproto.{handle}
|
||||
|
@ -463,7 +463,7 @@ function CustomHandleForm({
|
|||
</View>
|
||||
<View style={styles.spacer} />
|
||||
<Text type="md" style={[pal.text, s.pb5, s.pl5]}>
|
||||
That contains the following:
|
||||
<Trans>That contains the following:</Trans>
|
||||
</Text>
|
||||
<View style={[styles.valueContainer, pal.btn]}>
|
||||
<View style={[styles.dnsValue]}>
|
||||
|
@ -478,7 +478,9 @@ function CustomHandleForm({
|
|||
<View style={styles.spacer} />
|
||||
<Button type="default" style={[s.p20, s.mb10]} onPress={onPressCopy}>
|
||||
<Text type="xl" style={[pal.link, s.textCenter]}>
|
||||
Copy {isDNSForm ? 'Domain Value' : 'File Contents'}
|
||||
<Trans>
|
||||
Copy {isDNSForm ? _(msg`Domain Value`) : _(msg`File Contents`)}
|
||||
</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
{canSave === true && (
|
||||
|
@ -504,8 +506,8 @@ function CustomHandleForm({
|
|||
) : (
|
||||
<Text type="xl-medium" style={[s.white, s.textCenter]}>
|
||||
{canSave
|
||||
? `Update to ${handle}`
|
||||
: `Verify ${isDNSForm ? 'DNS Record' : 'Text File'}`}
|
||||
? _(msg`Update to ${handle}`)
|
||||
: _(msg`Verify ${isDNSForm ? 'DNS Record' : 'Text File'}`)}
|
||||
</Text>
|
||||
)}
|
||||
</Button>
|
||||
|
@ -513,9 +515,9 @@ function CustomHandleForm({
|
|||
<TouchableOpacity
|
||||
onPress={onToggleCustom}
|
||||
accessibilityLabel={_(msg`Use default provider`)}
|
||||
accessibilityHint="Use bsky.social as hosting provider">
|
||||
accessibilityHint={_(msg`Use bsky.social as hosting provider`)}>
|
||||
<Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
|
||||
Nevermind, create a handle for me
|
||||
<Trans>Nevermind, create a handle for me</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
|
|
|
@ -137,7 +137,9 @@ export function Component() {
|
|||
<View>
|
||||
<View style={styles.titleSection}>
|
||||
<Text type="title-lg" style={[pal.text, styles.title]}>
|
||||
{stage !== Stages.Done ? 'Change Password' : 'Password Changed'}
|
||||
{stage !== Stages.Done
|
||||
? _(msg`Change Password`)
|
||||
: _(msg`Password Changed`)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
@ -180,7 +182,7 @@ export function Component() {
|
|||
<TextInput
|
||||
testID="codeInput"
|
||||
style={[pal.text, styles.textInput]}
|
||||
placeholder="Reset code"
|
||||
placeholder={_(msg`Reset code`)}
|
||||
placeholderTextColor={pal.colors.textLight}
|
||||
value={resetCode}
|
||||
onChangeText={setResetCode}
|
||||
|
@ -207,7 +209,7 @@ export function Component() {
|
|||
<TextInput
|
||||
testID="codeInput"
|
||||
style={[pal.text, styles.textInput]}
|
||||
placeholder="New password"
|
||||
placeholder={_(msg`New password`)}
|
||||
placeholderTextColor={pal.colors.textLight}
|
||||
onChangeText={setNewPassword}
|
||||
secureTextEntry
|
||||
|
|
|
@ -173,7 +173,7 @@ export function Component({}: {}) {
|
|||
</Text>
|
||||
<TextInput
|
||||
style={[styles.textInput, pal.borderDark, pal.text, styles.mb20]}
|
||||
placeholder="Confirmation code"
|
||||
placeholder={_(msg`Confirmation code`)}
|
||||
placeholderTextColor={pal.textLight.color}
|
||||
keyboardAppearance={theme.colorScheme}
|
||||
value={confirmCode}
|
||||
|
@ -192,7 +192,7 @@ export function Component({}: {}) {
|
|||
</Text>
|
||||
<TextInput
|
||||
style={[styles.textInput, pal.borderDark, pal.text]}
|
||||
placeholder="Password"
|
||||
placeholder={_(msg`Password`)}
|
||||
placeholderTextColor={pal.textLight.color}
|
||||
keyboardAppearance={theme.colorScheme}
|
||||
secureTextEntry
|
||||
|
@ -228,7 +228,7 @@ export function Component({}: {}) {
|
|||
onPress={onCancel}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Cancel account deletion`)}
|
||||
accessibilityHint="Exits account deletion process"
|
||||
accessibilityHint={_(msg`Exits account deletion process`)}
|
||||
onAccessibilityEscape={onCancel}>
|
||||
<Text type="button-lg" style={pal.textLight}>
|
||||
<Trans context="action">Cancel</Trans>
|
||||
|
|
|
@ -77,7 +77,7 @@ export function Component({href}: {href: string}) {
|
|||
}}
|
||||
accessibilityLabel={_(msg`Cancel`)}
|
||||
accessibilityHint=""
|
||||
label="Cancel"
|
||||
label={_(msg`Cancel`)}
|
||||
labelContainerStyle={{justifyContent: 'center', padding: 8}}
|
||||
labelStyle={[s.f18]}
|
||||
/>
|
||||
|
|
|
@ -73,8 +73,8 @@ export function Component({text, href}: {text: string; href: string}) {
|
|||
type="primary"
|
||||
onPress={onPressVisit}
|
||||
accessibilityLabel={_(msg`Visit Site`)}
|
||||
accessibilityHint=""
|
||||
label="Visit Site"
|
||||
accessibilityHint={_(msg`Opens the linked website`)}
|
||||
label={_(msg`Visit Site`)}
|
||||
labelContainerStyle={{justifyContent: 'center', padding: 4}}
|
||||
labelStyle={[s.f18]}
|
||||
/>
|
||||
|
@ -85,8 +85,8 @@ export function Component({text, href}: {text: string; href: string}) {
|
|||
closeModal()
|
||||
}}
|
||||
accessibilityLabel={_(msg`Cancel`)}
|
||||
accessibilityHint=""
|
||||
label="Cancel"
|
||||
accessibilityHint={_(msg`Cancels opening the linked website`)}
|
||||
label={_(msg`Cancel`)}
|
||||
labelContainerStyle={{justifyContent: 'center', padding: 4}}
|
||||
labelStyle={[s.f18]}
|
||||
/>
|
||||
|
|
|
@ -231,7 +231,11 @@ function UserResult({
|
|||
width: 54,
|
||||
paddingLeft: 4,
|
||||
}}>
|
||||
<UserAvatar size={40} avatar={profile.avatar} />
|
||||
<UserAvatar
|
||||
size={40}
|
||||
avatar={profile.avatar}
|
||||
type={profile.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
|
|
|
@ -45,7 +45,11 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
|
|||
const contents = (
|
||||
<View style={[pal.view, styles.linkCard]}>
|
||||
<View style={styles.avi}>
|
||||
<UserAvatar size={40} avatar={profile?.avatar} />
|
||||
<UserAvatar
|
||||
size={40}
|
||||
avatar={profile?.avatar}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
<View style={[s.flex1]}>
|
||||
<Text type="md-bold" style={pal.text} numberOfLines={1}>
|
||||
|
|
|
@ -180,7 +180,7 @@ function ListItem({
|
|||
},
|
||||
]}>
|
||||
<View style={styles.listItemAvi}>
|
||||
<UserAvatar size={40} avatar={list.avatar} />
|
||||
<UserAvatar size={40} avatar={list.avatar} type="list" />
|
||||
</View>
|
||||
<View style={styles.listItemContent}>
|
||||
<Text
|
||||
|
|
|
@ -149,7 +149,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
|
|||
onPress={onEmailIncorrect}
|
||||
style={styles.changeEmailLink}>
|
||||
<Text type="lg" style={pal.link}>
|
||||
Change
|
||||
<Trans>Change</Trans>
|
||||
</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
|
|
|
@ -100,7 +100,7 @@ export function Component({
|
|||
onPress={doSetAs(AspectRatio.Wide)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Wide`)}
|
||||
accessibilityHint="Sets image aspect ratio to wide">
|
||||
accessibilityHint={_(msg`Sets image aspect ratio to wide`)}>
|
||||
<RectWideIcon
|
||||
size={24}
|
||||
style={as === AspectRatio.Wide ? s.blue3 : pal.text}
|
||||
|
@ -110,7 +110,7 @@ export function Component({
|
|||
onPress={doSetAs(AspectRatio.Tall)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Tall`)}
|
||||
accessibilityHint="Sets image aspect ratio to tall">
|
||||
accessibilityHint={_(msg`Sets image aspect ratio to tall`)}>
|
||||
<RectTallIcon
|
||||
size={24}
|
||||
style={as === AspectRatio.Tall ? s.blue3 : pal.text}
|
||||
|
@ -120,7 +120,7 @@ export function Component({
|
|||
onPress={doSetAs(AspectRatio.Square)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Square`)}
|
||||
accessibilityHint="Sets image aspect ratio to square">
|
||||
accessibilityHint={_(msg`Sets image aspect ratio to square`)}>
|
||||
<SquareIcon
|
||||
size={24}
|
||||
style={as === AspectRatio.Square ? s.blue3 : pal.text}
|
||||
|
@ -132,9 +132,9 @@ export function Component({
|
|||
onPress={onPressCancel}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Cancel image crop`)}
|
||||
accessibilityHint="Exits image cropping process">
|
||||
accessibilityHint={_(msg`Exits image cropping process`)}>
|
||||
<Text type="xl" style={pal.link}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
|
@ -142,7 +142,7 @@ export function Component({
|
|||
onPress={onPressDone}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Save image crop`)}
|
||||
accessibilityHint="Saves image crop settings">
|
||||
accessibilityHint={_(msg`Saves image crop settings`)}>
|
||||
<LinearGradient
|
||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
ModerationDecision,
|
||||
moderateProfile,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyActorDefs,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {
|
||||
|
@ -55,6 +56,7 @@ interface Author {
|
|||
displayName?: string
|
||||
avatar?: string
|
||||
moderation: ModerationDecision
|
||||
associated?: AppBskyActorDefs.ProfileAssociated
|
||||
}
|
||||
|
||||
let FeedItem = ({
|
||||
|
@ -100,6 +102,7 @@ let FeedItem = ({
|
|||
displayName: item.notification.author.displayName,
|
||||
avatar: item.notification.author.avatar,
|
||||
moderation: moderateProfile(item.notification.author, moderationOpts),
|
||||
associated: item.notification.author.associated,
|
||||
},
|
||||
...(item.additional?.map(({author}) => {
|
||||
return {
|
||||
|
@ -109,6 +112,7 @@ let FeedItem = ({
|
|||
displayName: author.displayName,
|
||||
avatar: author.avatar,
|
||||
moderation: moderateProfile(author, moderationOpts),
|
||||
associated: author.associated,
|
||||
}
|
||||
}) || []),
|
||||
]
|
||||
|
@ -337,6 +341,7 @@ function CondensedAuthorsList({
|
|||
handle={authors[0].handle}
|
||||
avatar={authors[0].avatar}
|
||||
moderation={authors[0].moderation.ui('avatar')}
|
||||
type={authors[0].associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
@ -355,6 +360,7 @@ function CondensedAuthorsList({
|
|||
size={35}
|
||||
avatar={author.avatar}
|
||||
moderation={author.moderation.ui('avatar')}
|
||||
type={author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
|
@ -413,6 +419,7 @@ function ExpandedAuthorsList({
|
|||
size={35}
|
||||
avatar={author.avatar}
|
||||
moderation={author.moderation.ui('avatar')}
|
||||
type={author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
<View style={s.flex1}>
|
||||
|
|
|
@ -1,25 +1,14 @@
|
|||
import React, {useEffect, useRef} from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {StyleSheet, useWindowDimensions, View} from 'react-native'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {CenteredView} from '../util/Views'
|
||||
import {LoadingScreen} from '../util/LoadingScreen'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {List, ListMethods} from '../util/List'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {PostThreadItem} from './PostThreadItem'
|
||||
import {ComposePrompt} from '../composer/Prompt'
|
||||
import {ViewHeader} from '../util/ViewHeader'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {s} from 'lib/styles'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||
import {
|
||||
|
@ -30,21 +19,18 @@ import {
|
|||
usePostThreadQuery,
|
||||
sortThread,
|
||||
} from '#/state/queries/post-thread'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {
|
||||
UsePreferencesQueryResponse,
|
||||
useModerationOpts,
|
||||
usePreferencesQuery,
|
||||
} from '#/state/queries/preferences'
|
||||
import {useSession} from '#/state/session'
|
||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
|
||||
// FlatList maintainVisibleContentPosition breaks if too many items
|
||||
// are prepended. This seems to be an optimal number based on *shrug*.
|
||||
|
@ -58,9 +44,7 @@ const MAINTAIN_VISIBLE_CONTENT_POSITION = {
|
|||
|
||||
const TOP_COMPONENT = {_reactKey: '__top_component__'}
|
||||
const REPLY_PROMPT = {_reactKey: '__reply__'}
|
||||
const CHILD_SPINNER = {_reactKey: '__child_spinner__'}
|
||||
const LOAD_MORE = {_reactKey: '__load_more__'}
|
||||
const BOTTOM_COMPONENT = {_reactKey: '__bottom_component__'}
|
||||
|
||||
type YieldedItem = ThreadPost | ThreadBlocked | ThreadNotFound
|
||||
type RowItem =
|
||||
|
@ -68,9 +52,7 @@ type RowItem =
|
|||
// TODO: TS doesn't actually enforce it's one of these, it only enforces matching shape.
|
||||
| typeof TOP_COMPONENT
|
||||
| typeof REPLY_PROMPT
|
||||
| typeof CHILD_SPINNER
|
||||
| typeof LOAD_MORE
|
||||
| typeof BOTTOM_COMPONENT
|
||||
|
||||
type ThreadSkeletonParts = {
|
||||
parents: YieldedItem[]
|
||||
|
@ -78,6 +60,10 @@ type ThreadSkeletonParts = {
|
|||
replies: YieldedItem[]
|
||||
}
|
||||
|
||||
const keyExtractor = (item: RowItem) => {
|
||||
return item._reactKey
|
||||
}
|
||||
|
||||
export function PostThread({
|
||||
uri,
|
||||
onCanReply,
|
||||
|
@ -85,17 +71,30 @@ export function PostThread({
|
|||
}: {
|
||||
uri: string | undefined
|
||||
onCanReply: (canReply: boolean) => void
|
||||
onPressReply: () => void
|
||||
onPressReply: () => unknown
|
||||
}) {
|
||||
const {hasSession} = useSession()
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
const {height: windowHeight} = useWindowDimensions()
|
||||
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
isFetching,
|
||||
isError: isThreadError,
|
||||
error: threadError,
|
||||
refetch,
|
||||
data: thread,
|
||||
} = usePostThreadQuery(uri)
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
|
||||
const treeView = React.useMemo(
|
||||
() =>
|
||||
!!preferences?.threadViewPrefs?.lab_treeViewEnabled &&
|
||||
hasBranchingReplies(thread),
|
||||
[preferences?.threadViewPrefs, thread],
|
||||
)
|
||||
const rootPost = thread?.type === 'post' ? thread.post : undefined
|
||||
const rootPostRecord = thread?.type === 'post' ? thread.record : undefined
|
||||
|
||||
|
@ -105,7 +104,6 @@ export function PostThread({
|
|||
rootPost && moderationOpts
|
||||
? moderatePost(rootPost, moderationOpts)
|
||||
: undefined
|
||||
|
||||
return !!mod
|
||||
?.ui('contentList')
|
||||
.blurs.find(
|
||||
|
@ -114,6 +112,14 @@ export function PostThread({
|
|||
)
|
||||
}, [rootPost, moderationOpts])
|
||||
|
||||
// Values used for proper rendering of parents
|
||||
const ref = useRef<ListMethods>(null)
|
||||
const highlightedPostRef = useRef<View | null>(null)
|
||||
const [maxParents, setMaxParents] = React.useState(
|
||||
isWeb ? Infinity : PARENTS_CHUNK_SIZE,
|
||||
)
|
||||
const [maxReplies, setMaxReplies] = React.useState(50)
|
||||
|
||||
useSetTitle(
|
||||
rootPost && !isNoPwi
|
||||
? `${sanitizeDisplayName(
|
||||
|
@ -121,62 +127,6 @@ export function PostThread({
|
|||
)}: "${rootPostRecord!.text}"`
|
||||
: '',
|
||||
)
|
||||
useEffect(() => {
|
||||
if (rootPost) {
|
||||
onCanReply(!rootPost.viewer?.replyDisabled)
|
||||
}
|
||||
}, [rootPost, onCanReply])
|
||||
|
||||
if (isError || AppBskyFeedDefs.isNotFoundPost(thread)) {
|
||||
return (
|
||||
<PostThreadError
|
||||
error={error}
|
||||
notFound={AppBskyFeedDefs.isNotFoundPost(thread)}
|
||||
onRefresh={refetch}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (AppBskyFeedDefs.isBlockedPost(thread)) {
|
||||
return <PostThreadBlocked />
|
||||
}
|
||||
if (!thread || isLoading || !preferences) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
return (
|
||||
<PostThreadLoaded
|
||||
thread={thread}
|
||||
threadViewPrefs={preferences.threadViewPrefs}
|
||||
onRefresh={refetch}
|
||||
onPressReply={onPressReply}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function PostThreadLoaded({
|
||||
thread,
|
||||
threadViewPrefs,
|
||||
onRefresh,
|
||||
onPressReply,
|
||||
}: {
|
||||
thread: ThreadNode
|
||||
threadViewPrefs: UsePreferencesQueryResponse['threadViewPrefs']
|
||||
onRefresh: () => void
|
||||
onPressReply: () => void
|
||||
}) {
|
||||
const {hasSession} = useSession()
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
|
||||
const ref = useRef<ListMethods>(null)
|
||||
const highlightedPostRef = useRef<View | null>(null)
|
||||
const [maxParents, setMaxParents] = React.useState(
|
||||
isWeb ? Infinity : PARENTS_CHUNK_SIZE,
|
||||
)
|
||||
const [maxReplies, setMaxReplies] = React.useState(100)
|
||||
const treeView = React.useMemo(
|
||||
() => !!threadViewPrefs.lab_treeViewEnabled && hasBranchingReplies(thread),
|
||||
[threadViewPrefs, thread],
|
||||
)
|
||||
|
||||
// On native, this is going to start out `true`. We'll toggle it to `false` after the initial render if flushed.
|
||||
// This ensures that the first render contains no parents--even if they are already available in the cache.
|
||||
|
@ -184,18 +134,56 @@ function PostThreadLoaded({
|
|||
// On the web this is not necessary because we can synchronously adjust the scroll in onContentSizeChange instead.
|
||||
const [deferParents, setDeferParents] = React.useState(isNative)
|
||||
|
||||
const skeleton = React.useMemo(
|
||||
() =>
|
||||
createThreadSkeleton(
|
||||
sortThread(thread, threadViewPrefs),
|
||||
hasSession,
|
||||
treeView,
|
||||
),
|
||||
[thread, threadViewPrefs, hasSession, treeView],
|
||||
)
|
||||
const skeleton = React.useMemo(() => {
|
||||
const threadViewPrefs = preferences?.threadViewPrefs
|
||||
if (!threadViewPrefs || !thread) return null
|
||||
|
||||
return createThreadSkeleton(
|
||||
sortThread(thread, threadViewPrefs),
|
||||
hasSession,
|
||||
treeView,
|
||||
)
|
||||
}, [thread, preferences?.threadViewPrefs, hasSession, treeView])
|
||||
|
||||
const error = React.useMemo(() => {
|
||||
if (AppBskyFeedDefs.isNotFoundPost(thread)) {
|
||||
return {
|
||||
title: _(msg`Post not found`),
|
||||
message: _(msg`The post may have been deleted.`),
|
||||
}
|
||||
} else if (skeleton?.highlightedPost.type === 'blocked') {
|
||||
return {
|
||||
title: _(msg`Post hidden`),
|
||||
message: _(
|
||||
msg`You have blocked the author or you have been blocked by the author.`,
|
||||
),
|
||||
}
|
||||
} else if (threadError?.message.startsWith('Post not found')) {
|
||||
return {
|
||||
title: _(msg`Post not found`),
|
||||
message: _(msg`The post may have been deleted.`),
|
||||
}
|
||||
} else if (isThreadError) {
|
||||
return {
|
||||
message: threadError ? cleanError(threadError) : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}, [thread, skeleton?.highlightedPost, isThreadError, _, threadError])
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
onCanReply(false)
|
||||
} else if (rootPost) {
|
||||
onCanReply(!rootPost.viewer?.replyDisabled)
|
||||
}
|
||||
}, [rootPost, onCanReply, error])
|
||||
|
||||
// construct content
|
||||
const posts = React.useMemo(() => {
|
||||
if (!skeleton) return []
|
||||
|
||||
const {parents, highlightedPost, replies} = skeleton
|
||||
let arr: RowItem[] = []
|
||||
if (highlightedPost.type === 'post') {
|
||||
|
@ -231,17 +219,11 @@ function PostThreadLoaded({
|
|||
if (!highlightedPost.post.viewer?.replyDisabled) {
|
||||
arr.push(REPLY_PROMPT)
|
||||
}
|
||||
if (highlightedPost.ctx.isChildLoading) {
|
||||
arr.push(CHILD_SPINNER)
|
||||
} else {
|
||||
for (let i = 0; i < replies.length; i++) {
|
||||
arr.push(replies[i])
|
||||
if (i === maxReplies) {
|
||||
arr.push(LOAD_MORE)
|
||||
break
|
||||
}
|
||||
for (let i = 0; i < replies.length; i++) {
|
||||
arr.push(replies[i])
|
||||
if (i === maxReplies) {
|
||||
break
|
||||
}
|
||||
arr.push(BOTTOM_COMPONENT)
|
||||
}
|
||||
}
|
||||
return arr
|
||||
|
@ -256,7 +238,7 @@ function PostThreadLoaded({
|
|||
return
|
||||
}
|
||||
// wait for loading to finish
|
||||
if (thread.type === 'post' && !!thread.parent) {
|
||||
if (thread?.type === 'post' && !!thread.parent) {
|
||||
function onMeasure(pageY: number) {
|
||||
ref.current?.scrollToOffset({
|
||||
animated: false,
|
||||
|
@ -280,10 +262,10 @@ function PostThreadLoaded({
|
|||
// To work around this, we prepend rows after scroll bumps against the top and rests.
|
||||
const needsBumpMaxParents = React.useRef(false)
|
||||
const onStartReached = React.useCallback(() => {
|
||||
if (maxParents < skeleton.parents.length) {
|
||||
if (skeleton?.parents && maxParents < skeleton.parents.length) {
|
||||
needsBumpMaxParents.current = true
|
||||
}
|
||||
}, [maxParents, skeleton.parents.length])
|
||||
}, [maxParents, skeleton?.parents])
|
||||
const bumpMaxParentsIfNeeded = React.useCallback(() => {
|
||||
if (!isNative) {
|
||||
return
|
||||
|
@ -296,6 +278,11 @@ function PostThreadLoaded({
|
|||
const onMomentumScrollEnd = bumpMaxParentsIfNeeded
|
||||
const onScrollToTop = bumpMaxParentsIfNeeded
|
||||
|
||||
const onEndReached = React.useCallback(() => {
|
||||
if (isFetching || posts.length < maxReplies) return
|
||||
setMaxReplies(prev => prev + 50)
|
||||
}, [isFetching, maxReplies, posts.length])
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({item, index}: {item: RowItem; index: number}) => {
|
||||
if (item === TOP_COMPONENT) {
|
||||
|
@ -326,46 +313,6 @@ function PostThreadLoaded({
|
|||
</Text>
|
||||
</View>
|
||||
)
|
||||
} else if (item === LOAD_MORE) {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => setMaxReplies(n => n + 50)}
|
||||
style={[pal.border, pal.view, styles.itemContainer]}
|
||||
accessibilityLabel={_(msg`Load more posts`)}
|
||||
accessibilityHint="">
|
||||
<View
|
||||
style={[
|
||||
pal.viewLight,
|
||||
{paddingHorizontal: 18, paddingVertical: 14, borderRadius: 6},
|
||||
]}>
|
||||
<Text type="lg-medium" style={pal.text}>
|
||||
<Trans>Load more posts</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
} else if (item === BOTTOM_COMPONENT) {
|
||||
// HACK
|
||||
// due to some complexities with how flatlist works, this is the easiest way
|
||||
// I could find to get a border positioned directly under the last item
|
||||
// -prf
|
||||
return (
|
||||
<View
|
||||
// @ts-ignore web-only
|
||||
style={{
|
||||
// Leave enough space below that the scroll doesn't jump
|
||||
height: isNative ? 600 : '100vh',
|
||||
borderTopWidth: 1,
|
||||
borderColor: pal.colors.border,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
} else if (item === CHILD_SPINNER) {
|
||||
return (
|
||||
<View style={[pal.border, styles.childSpinner]}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
)
|
||||
} else if (isThreadPost(item)) {
|
||||
const prev = isThreadPost(posts[index - 1])
|
||||
? (posts[index - 1] as ThreadPost)
|
||||
|
@ -374,7 +321,9 @@ function PostThreadLoaded({
|
|||
? (posts[index - 1] as ThreadPost)
|
||||
: undefined
|
||||
const hasUnrevealedParents =
|
||||
index === 0 && maxParents < skeleton.parents.length
|
||||
index === 0 &&
|
||||
skeleton?.parents &&
|
||||
maxParents < skeleton.parents.length
|
||||
return (
|
||||
<View
|
||||
ref={item.ctx.isHighlightedPost ? highlightedPostRef : undefined}
|
||||
|
@ -391,9 +340,9 @@ function PostThreadLoaded({
|
|||
showChildReplyLine={item.ctx.showChildReplyLine}
|
||||
showParentReplyLine={item.ctx.showParentReplyLine}
|
||||
hasPrecedingItem={
|
||||
!!prev?.ctx.showChildReplyLine || hasUnrevealedParents
|
||||
!!prev?.ctx.showChildReplyLine || !!hasUnrevealedParents
|
||||
}
|
||||
onPostReply={onRefresh}
|
||||
onPostReply={refetch}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
@ -403,142 +352,62 @@ function PostThreadLoaded({
|
|||
[
|
||||
hasSession,
|
||||
isTabletOrMobile,
|
||||
_,
|
||||
isMobile,
|
||||
onPressReply,
|
||||
pal.border,
|
||||
pal.viewLight,
|
||||
pal.textLight,
|
||||
pal.view,
|
||||
pal.text,
|
||||
pal.colors.border,
|
||||
posts,
|
||||
onRefresh,
|
||||
skeleton?.parents,
|
||||
maxParents,
|
||||
deferParents,
|
||||
treeView,
|
||||
skeleton.parents.length,
|
||||
maxParents,
|
||||
_,
|
||||
refetch,
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={ref}
|
||||
data={posts}
|
||||
keyExtractor={item => item._reactKey}
|
||||
renderItem={renderItem}
|
||||
onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
|
||||
onStartReached={onStartReached}
|
||||
onMomentumScrollEnd={onMomentumScrollEnd}
|
||||
onScrollToTop={onScrollToTop}
|
||||
maintainVisibleContentPosition={
|
||||
isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined
|
||||
}
|
||||
style={s.hContentRegion}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
removeClippedSubviews={isAndroid ? false : undefined}
|
||||
windowSize={11}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function PostThreadBlocked() {
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
const onPressBack = React.useCallback(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
return (
|
||||
<CenteredView>
|
||||
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
||||
<Text type="title-lg" style={[pal.text, s.mb5]}>
|
||||
<Trans>Post hidden</Trans>
|
||||
</Text>
|
||||
<Text type="md" style={[pal.text, s.mb10]}>
|
||||
<Trans>
|
||||
You have blocked the author or you have been blocked by the author.
|
||||
</Trans>
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={onPressBack}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Back`)}
|
||||
accessibilityHint="">
|
||||
<Text type="2xl" style={pal.link}>
|
||||
<FontAwesomeIcon
|
||||
icon="angle-left"
|
||||
style={[pal.link as FontAwesomeIconStyle, s.mr5]}
|
||||
size={14}
|
||||
<>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={!preferences || !thread}
|
||||
isError={!!error}
|
||||
onRetry={refetch}
|
||||
errorTitle={error?.title}
|
||||
errorMessage={error?.message}
|
||||
/>
|
||||
{!error && thread && (
|
||||
<List
|
||||
ref={ref}
|
||||
data={posts}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
|
||||
onStartReached={onStartReached}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={2}
|
||||
onMomentumScrollEnd={onMomentumScrollEnd}
|
||||
onScrollToTop={onScrollToTop}
|
||||
maintainVisibleContentPosition={
|
||||
isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined
|
||||
}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
removeClippedSubviews={isAndroid ? false : undefined}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetching={isFetching}
|
||||
onRetry={refetch}
|
||||
// 300 is based on the minimum height of a post. This is enough extra height for the `maintainVisPos` to
|
||||
// work without causing weird jumps on web or glitches on native
|
||||
height={windowHeight - 200}
|
||||
/>
|
||||
<Trans context="action">Back</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
|
||||
function PostThreadError({
|
||||
onRefresh,
|
||||
notFound,
|
||||
error,
|
||||
}: {
|
||||
onRefresh: () => void
|
||||
notFound: boolean
|
||||
error: Error | null
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
const onPressBack = React.useCallback(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
if (notFound) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
||||
<Text type="title-lg" style={[pal.text, s.mb5]}>
|
||||
<Trans>Post not found</Trans>
|
||||
</Text>
|
||||
<Text type="md" style={[pal.text, s.mb10]}>
|
||||
<Trans>The post may have been deleted.</Trans>
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={onPressBack}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Back`)}
|
||||
accessibilityHint="">
|
||||
<Text type="2xl" style={pal.link}>
|
||||
<FontAwesomeIcon
|
||||
icon="angle-left"
|
||||
style={[pal.link as FontAwesomeIconStyle, s.mr5]}
|
||||
size={14}
|
||||
/>
|
||||
<Trans>Back</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<CenteredView>
|
||||
<ErrorMessage message={cleanError(error)} onPressTryAgain={onRefresh} />
|
||||
</CenteredView>
|
||||
}
|
||||
initialNumToRender={initialNumToRender}
|
||||
windowSize={11}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -558,7 +427,9 @@ function createThreadSkeleton(
|
|||
node: ThreadNode,
|
||||
hasSession: boolean,
|
||||
treeView: boolean,
|
||||
): ThreadSkeletonParts {
|
||||
): ThreadSkeletonParts | null {
|
||||
if (!node) return null
|
||||
|
||||
return {
|
||||
parents: Array.from(flattenThreadParents(node, hasSession)),
|
||||
highlightedPost: node,
|
||||
|
@ -615,7 +486,10 @@ function hasPwiOptOut(node: ThreadPost) {
|
|||
return !!node.post.author.labels?.find(l => l.val === '!no-unauthenticated')
|
||||
}
|
||||
|
||||
function hasBranchingReplies(node: ThreadNode) {
|
||||
function hasBranchingReplies(node?: ThreadNode) {
|
||||
if (!node) {
|
||||
return false
|
||||
}
|
||||
if (node.type !== 'post') {
|
||||
return false
|
||||
}
|
||||
|
@ -629,20 +503,9 @@ function hasBranchingReplies(node: ThreadNode) {
|
|||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
notFoundContainer: {
|
||||
margin: 10,
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 6,
|
||||
},
|
||||
itemContainer: {
|
||||
borderTopWidth: 1,
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 18,
|
||||
},
|
||||
childSpinner: {
|
||||
borderTopWidth: 1,
|
||||
paddingTop: 40,
|
||||
paddingBottom: 200,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -205,11 +205,7 @@ let PostThreadItemLoaded = ({
|
|||
uri: post.uri,
|
||||
cid: post.cid,
|
||||
text: record.text,
|
||||
author: {
|
||||
handle: post.author.handle,
|
||||
displayName: post.author.displayName,
|
||||
avatar: post.author.avatar,
|
||||
},
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
},
|
||||
|
@ -256,6 +252,7 @@ let PostThreadItemLoaded = ({
|
|||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
|
@ -452,6 +449,7 @@ let PostThreadItemLoaded = ({
|
|||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
||||
{showChildReplyLine && (
|
||||
|
@ -540,7 +538,7 @@ let PostThreadItemLoaded = ({
|
|||
title={itemTitle}
|
||||
noFeedback>
|
||||
<Text type="sm-medium" style={pal.textLight}>
|
||||
More
|
||||
<Trans>More</Trans>
|
||||
</Text>
|
||||
<FontAwesomeIcon
|
||||
icon="angle-right"
|
||||
|
|
|
@ -118,11 +118,7 @@ function PostInner({
|
|||
uri: post.uri,
|
||||
cid: post.cid,
|
||||
text: record.text,
|
||||
author: {
|
||||
handle: post.author.handle,
|
||||
displayName: post.author.displayName,
|
||||
avatar: post.author.avatar,
|
||||
},
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
},
|
||||
|
@ -144,6 +140,7 @@ function PostInner({
|
|||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
|
|
|
@ -126,11 +126,7 @@ let FeedItemInner = ({
|
|||
uri: post.uri,
|
||||
cid: post.cid,
|
||||
text: record.text || '',
|
||||
author: {
|
||||
handle: post.author.handle,
|
||||
displayName: post.author.displayName,
|
||||
avatar: post.author.avatar,
|
||||
},
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
},
|
||||
|
@ -243,6 +239,7 @@ let FeedItemInner = ({
|
|||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
{isThreadParent && (
|
||||
<View
|
||||
|
|
|
@ -49,6 +49,7 @@ export function ProfileCard({
|
|||
const pal = usePalette('default')
|
||||
const profile = useProfileShadow(profileUnshadowed)
|
||||
const moderationOpts = useModerationOpts()
|
||||
const isLabeler = profile?.associated?.labeler
|
||||
if (!moderationOpts) {
|
||||
return null
|
||||
}
|
||||
|
@ -79,6 +80,7 @@ export function ProfileCard({
|
|||
size={40}
|
||||
avatar={profile.avatar}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={isLabeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
|
@ -101,7 +103,7 @@ export function ProfileCard({
|
|||
/>
|
||||
{!!profile.viewer?.followedBy && <View style={s.flexRow} />}
|
||||
</View>
|
||||
{renderButton ? (
|
||||
{renderButton && !isLabeler ? (
|
||||
<View style={styles.layoutButton}>{renderButton(profile)}</View>
|
||||
) : undefined}
|
||||
</View>
|
||||
|
@ -223,6 +225,7 @@ function FollowersList({
|
|||
avatar={f.avatar}
|
||||
size={32}
|
||||
moderation={mod.ui('avatar')}
|
||||
type={f.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -1,39 +1,66 @@
|
|||
import React from 'react'
|
||||
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
||||
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
||||
import {CenteredView} from '../util/Views'
|
||||
import {LoadingScreen} from '../util/LoadingScreen'
|
||||
import {List} from '../util/List'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {logger} from '#/logger'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {
|
||||
ListFooter,
|
||||
ListHeaderDesktop,
|
||||
ListMaybePlaceholder,
|
||||
} from '#/components/Lists'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useSession} from 'state/session'
|
||||
import {View} from 'react-native'
|
||||
|
||||
function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
|
||||
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
|
||||
}
|
||||
|
||||
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
|
||||
return item.did
|
||||
}
|
||||
|
||||
export function ProfileFollowers({name}: {name: string}) {
|
||||
const {_} = useLingui()
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||
const {
|
||||
data: resolvedDid,
|
||||
isLoading: isDidLoading,
|
||||
error: resolveError,
|
||||
isFetching: isFetchingDid,
|
||||
} = useResolveDidQuery(name)
|
||||
const {
|
||||
data,
|
||||
isLoading: isFollowersLoading,
|
||||
isFetching,
|
||||
isFetched,
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isError,
|
||||
error,
|
||||
refetch,
|
||||
} = useProfileFollowersQuery(resolvedDid)
|
||||
|
||||
const isError = React.useMemo(
|
||||
() => !!resolveError || !!error,
|
||||
[resolveError, error],
|
||||
)
|
||||
|
||||
const isMe = React.useMemo(() => {
|
||||
return resolvedDid === currentAccount?.did
|
||||
}, [resolvedDid, currentAccount?.did])
|
||||
|
||||
const followers = React.useMemo(() => {
|
||||
if (data?.pages) {
|
||||
return data.pages.flatMap(page => page.followers)
|
||||
}
|
||||
return []
|
||||
}, [data])
|
||||
|
||||
const onRefresh = React.useCallback(async () => {
|
||||
|
@ -47,7 +74,7 @@ export function ProfileFollowers({name}: {name: string}) {
|
|||
}, [refetch, setIsPTRing])
|
||||
|
||||
const onEndReached = async () => {
|
||||
if (isFetching || !hasNextPage || isError) return
|
||||
if (isFetching || !hasNextPage || !!error) return
|
||||
try {
|
||||
await fetchNextPage()
|
||||
} catch (err) {
|
||||
|
@ -55,57 +82,38 @@ export function ProfileFollowers({name}: {name: string}) {
|
|||
}
|
||||
}
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({item}: {item: ActorDefs.ProfileViewBasic}) => (
|
||||
<ProfileCardWithFollowBtn key={item.did} profile={item} />
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
if (isFetchingDid || !isFetched) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
|
||||
// error
|
||||
// =
|
||||
if (resolveError || isError) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<ErrorMessage
|
||||
message={cleanError(resolveError || error)}
|
||||
onPressTryAgain={onRefresh}
|
||||
/>
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
|
||||
// loaded
|
||||
// =
|
||||
return (
|
||||
<List
|
||||
data={followers}
|
||||
keyExtractor={item => item.did}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
renderItem={renderItem}
|
||||
initialNumToRender={15}
|
||||
// FIXME(dan)
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
ListFooterComponent={() => (
|
||||
<View style={styles.footer}>
|
||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
||||
</View>
|
||||
<View style={{flex: 1}}>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isDidLoading || isFollowersLoading}
|
||||
isEmpty={followers.length < 1}
|
||||
isError={isError}
|
||||
emptyType="results"
|
||||
emptyMessage={
|
||||
isMe
|
||||
? _(msg`You do not have any followers.`)
|
||||
: _(msg`This user doesn't have any followers.`)
|
||||
}
|
||||
errorMessage={cleanError(resolveError || error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
{followers.length > 0 && (
|
||||
<List
|
||||
data={followers}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={4}
|
||||
ListHeaderComponent={<ListHeaderDesktop title={_(msg`Followers`)} />}
|
||||
ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
initialNumToRender={initialNumToRender}
|
||||
windowSize={11}
|
||||
/>
|
||||
)}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
footer: {
|
||||
height: 200,
|
||||
paddingTop: 20,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,39 +1,65 @@
|
|||
import React from 'react'
|
||||
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
||||
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
||||
import {CenteredView} from '../util/Views'
|
||||
import {LoadingScreen} from '../util/LoadingScreen'
|
||||
import {List} from '../util/List'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {logger} from '#/logger'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {
|
||||
ListFooter,
|
||||
ListHeaderDesktop,
|
||||
ListMaybePlaceholder,
|
||||
} from '#/components/Lists'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {useSession} from 'state/session'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
|
||||
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
|
||||
}
|
||||
|
||||
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
|
||||
return item.did
|
||||
}
|
||||
|
||||
export function ProfileFollows({name}: {name: string}) {
|
||||
const {_} = useLingui()
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||
const {
|
||||
data: resolvedDid,
|
||||
isLoading: isDidLoading,
|
||||
error: resolveError,
|
||||
isFetching: isFetchingDid,
|
||||
} = useResolveDidQuery(name)
|
||||
const {
|
||||
data,
|
||||
isLoading: isFollowsLoading,
|
||||
isFetching,
|
||||
isFetched,
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isError,
|
||||
error,
|
||||
refetch,
|
||||
} = useProfileFollowsQuery(resolvedDid)
|
||||
|
||||
const isError = React.useMemo(
|
||||
() => !!resolveError || !!error,
|
||||
[resolveError, error],
|
||||
)
|
||||
|
||||
const isMe = React.useMemo(() => {
|
||||
return resolvedDid === currentAccount?.did
|
||||
}, [resolvedDid, currentAccount?.did])
|
||||
|
||||
const follows = React.useMemo(() => {
|
||||
if (data?.pages) {
|
||||
return data.pages.flatMap(page => page.follows)
|
||||
}
|
||||
return []
|
||||
}, [data])
|
||||
|
||||
const onRefresh = React.useCallback(async () => {
|
||||
|
@ -47,7 +73,7 @@ export function ProfileFollows({name}: {name: string}) {
|
|||
}, [refetch, setIsPTRing])
|
||||
|
||||
const onEndReached = async () => {
|
||||
if (isFetching || !hasNextPage || isError) return
|
||||
if (isFetching || !hasNextPage || !!error) return
|
||||
try {
|
||||
await fetchNextPage()
|
||||
} catch (err) {
|
||||
|
@ -55,57 +81,38 @@ export function ProfileFollows({name}: {name: string}) {
|
|||
}
|
||||
}
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({item}: {item: ActorDefs.ProfileViewBasic}) => (
|
||||
<ProfileCardWithFollowBtn key={item.did} profile={item} />
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
if (isFetchingDid || !isFetched) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
|
||||
// error
|
||||
// =
|
||||
if (resolveError || isError) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<ErrorMessage
|
||||
message={cleanError(resolveError || error)}
|
||||
onPressTryAgain={onRefresh}
|
||||
/>
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
|
||||
// loaded
|
||||
// =
|
||||
return (
|
||||
<List
|
||||
data={follows}
|
||||
keyExtractor={item => item.did}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
renderItem={renderItem}
|
||||
initialNumToRender={15}
|
||||
// FIXME(dan)
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
ListFooterComponent={() => (
|
||||
<View style={styles.footer}>
|
||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
||||
</View>
|
||||
<>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isDidLoading || isFollowsLoading}
|
||||
isEmpty={follows.length < 1}
|
||||
isError={isError}
|
||||
emptyType="results"
|
||||
emptyMessage={
|
||||
isMe
|
||||
? _(msg`You are not following anyone.`)
|
||||
: _(msg`This user isn't following anyone.`)
|
||||
}
|
||||
errorMessage={cleanError(resolveError || error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
{follows.length > 0 && (
|
||||
<List
|
||||
data={follows}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={4}
|
||||
ListHeaderComponent={<ListHeaderDesktop title={_(msg`Following`)} />}
|
||||
ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
initialNumToRender={initialNumToRender}
|
||||
windowSize={11}
|
||||
/>
|
||||
)}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
footer: {
|
||||
height: 200,
|
||||
paddingTop: 20,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -21,7 +21,8 @@ import {useModerationOpts} from '#/state/queries/preferences'
|
|||
import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
|
||||
const OUTER_PADDING = 10
|
||||
const INNER_PADDING = 14
|
||||
|
@ -98,9 +99,11 @@ export function ProfileHeaderSuggestedFollows({
|
|||
<SuggestedFollowSkeleton />
|
||||
</>
|
||||
) : data ? (
|
||||
data.suggestions.map(profile => (
|
||||
<SuggestedFollow key={profile.did} profile={profile} />
|
||||
))
|
||||
data.suggestions
|
||||
.filter(s => (s.associated?.labeler ? false : true))
|
||||
.map(profile => (
|
||||
<SuggestedFollow key={profile.did} profile={profile} />
|
||||
))
|
||||
) : (
|
||||
<View />
|
||||
)}
|
||||
|
@ -168,6 +171,7 @@ function SuggestedFollow({
|
|||
}) {
|
||||
const {track} = useAnalytics()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const profile = useProfileShadow(profileUnshadowed)
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||
|
@ -181,20 +185,20 @@ function SuggestedFollow({
|
|||
await queueFollow()
|
||||
} catch (e: any) {
|
||||
if (e?.name !== 'AbortError') {
|
||||
Toast.show('An issue occurred, please try again.')
|
||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
||||
}
|
||||
}
|
||||
}, [queueFollow, track])
|
||||
}, [queueFollow, track, _])
|
||||
|
||||
const onPressUnfollow = React.useCallback(async () => {
|
||||
try {
|
||||
await queueUnfollow()
|
||||
} catch (e: any) {
|
||||
if (e?.name !== 'AbortError') {
|
||||
Toast.show('An issue occurred, please try again.')
|
||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
||||
}
|
||||
}
|
||||
}, [queueUnfollow])
|
||||
}, [queueUnfollow, _])
|
||||
|
||||
if (!moderationOpts) {
|
||||
return null
|
||||
|
@ -239,7 +243,7 @@ function SuggestedFollow({
|
|||
</View>
|
||||
|
||||
<Button
|
||||
label={following ? 'Unfollow' : 'Follow'}
|
||||
label={following ? _(msg`Unfollow`) : _(msg`Follow`)}
|
||||
type="inverted"
|
||||
labelStyle={{textAlign: 'center'}}
|
||||
onPress={following ? onPressUnfollow : onPressFollow}
|
||||
|
|
|
@ -11,16 +11,11 @@ import {sanitizeHandle} from 'lib/strings/handles'
|
|||
import {isAndroid, isWeb} from 'platform/detection'
|
||||
import {TimeElapsed} from './TimeElapsed'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {ModerationDecision, ModerationUI} from '@atproto/api'
|
||||
import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api'
|
||||
import {usePrefetchProfileQuery} from '#/state/queries/profile'
|
||||
|
||||
interface PostMetaOpts {
|
||||
author: {
|
||||
avatar?: string
|
||||
did: string
|
||||
handle: string
|
||||
displayName?: string | undefined
|
||||
}
|
||||
author: AppBskyActorDefs.ProfileViewBasic
|
||||
moderation: ModerationDecision | undefined
|
||||
authorHasWarning: boolean
|
||||
postHref: string
|
||||
|
@ -47,6 +42,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||
avatar={opts.author.avatar}
|
||||
size={opts.avatarSize || 16}
|
||||
moderation={opts.avatarModeration}
|
||||
type={opts.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
|
|
@ -97,7 +97,7 @@ export function LanguageSettingsScreen(_props: Props) {
|
|||
<Text style={[pal.text, s.pb10]}>
|
||||
<Trans>
|
||||
Select your app language for the default text to display in the
|
||||
app
|
||||
app.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
|
@ -296,7 +296,7 @@ export function LanguageSettingsScreen(_props: Props) {
|
|||
type="button"
|
||||
style={[pal.text, {flexShrink: 1, overflow: 'hidden'}]}
|
||||
numberOfLines={1}>
|
||||
{myLanguages.length ? myLanguages : 'Select languages'}
|
||||
{myLanguages.length ? myLanguages : _(msg`Select languages`)}
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
|
|
|
@ -51,7 +51,13 @@ export const NotFoundScreen = () => {
|
|||
</Text>
|
||||
<Button
|
||||
type="primary"
|
||||
label={canGoBack ? 'Go back' : 'Go home'}
|
||||
label={canGoBack ? _(msg`Go Back`) : _(msg`Go Home`)}
|
||||
accessibilityLabel={canGoBack ? _(msg`Go back`) : _(msg`Go home`)}
|
||||
accessibilityHint={
|
||||
canGoBack
|
||||
? _(msg`Returns to previous page`)
|
||||
: _(msg`Returns to home page`)
|
||||
}
|
||||
onPress={onPressHome}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -59,11 +59,7 @@ export function PostThreadScreen({route}: Props) {
|
|||
uri: thread.post.uri,
|
||||
cid: thread.post.cid,
|
||||
text: thread.record.text,
|
||||
author: {
|
||||
handle: thread.post.author.handle,
|
||||
displayName: thread.post.author.displayName,
|
||||
avatar: thread.post.author.avatar,
|
||||
},
|
||||
author: thread.post.author,
|
||||
embed: thread.post.embed,
|
||||
},
|
||||
onPost: () =>
|
||||
|
|
|
@ -108,8 +108,8 @@ export function ProfileFeedScreen(props: Props) {
|
|||
<View style={{flexDirection: 'row'}}>
|
||||
<Button
|
||||
type="default"
|
||||
accessibilityLabel={_(msg`Go Back`)}
|
||||
accessibilityHint="Return to previous page"
|
||||
accessibilityLabel={_(msg`Go back`)}
|
||||
accessibilityHint={_(msg`Returns to previous page`)}
|
||||
onPress={onPressBack}
|
||||
style={{flexShrink: 1}}>
|
||||
<Text type="button" style={pal.text}>
|
||||
|
|
|
@ -21,7 +21,7 @@ export const ProfileFollowersScreen = ({route}: Props) => {
|
|||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={{flex: 1}}>
|
||||
<ViewHeader title={_(msg`Followers`)} />
|
||||
<ProfileFollowersComponent name={name} />
|
||||
</View>
|
||||
|
|
|
@ -21,7 +21,7 @@ export const ProfileFollowsScreen = ({route}: Props) => {
|
|||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={{flex: 1}}>
|
||||
<ViewHeader title={_(msg`Following`)} />
|
||||
<ProfileFollowsComponent name={name} />
|
||||
</View>
|
||||
|
|
|
@ -913,7 +913,7 @@ function ErrorScreen({error}: {error: string}) {
|
|||
<View style={{flexDirection: 'row'}}>
|
||||
<Button
|
||||
type="default"
|
||||
accessibilityLabel={_(msg`Go Back`)}
|
||||
accessibilityLabel={_(msg`Go back`)}
|
||||
accessibilityHint={_(msg`Return to previous page`)}
|
||||
onPress={onPressBack}
|
||||
style={{flexShrink: 1}}>
|
||||
|
|
|
@ -141,6 +141,7 @@ function SearchScreenSuggestedFollows() {
|
|||
friends.slice(0, 4).map(friend =>
|
||||
getSuggestedFollowsByActor(friend.did).then(foafsRes => {
|
||||
for (const user of foafsRes.suggestions) {
|
||||
if (user.associated?.labeler) continue
|
||||
friendsOfFriends.set(user.did, user)
|
||||
}
|
||||
}),
|
||||
|
@ -772,7 +773,7 @@ export function SearchScreen(
|
|||
{searchHistory.length > 0 && (
|
||||
<View style={styles.searchHistoryContent}>
|
||||
<Text style={[pal.text, styles.searchHistoryTitle]}>
|
||||
Recent Searches
|
||||
<Trans>Recent Searches</Trans>
|
||||
</Text>
|
||||
{searchHistory.map((historyItem, index) => (
|
||||
<View key={index} style={styles.historyItemContainer}>
|
||||
|
|
|
@ -78,8 +78,9 @@ export function ExportCarDialog({
|
|||
<InlineLink
|
||||
to="https://docs.bsky.app/blog/repo-export"
|
||||
style={[a.text_sm]}>
|
||||
this blogpost.
|
||||
this blogpost
|
||||
</InlineLink>
|
||||
.
|
||||
</Trans>
|
||||
</P>
|
||||
|
||||
|
|
|
@ -40,10 +40,7 @@ import {
|
|||
} from '#/state/preferences'
|
||||
import {useSession, useSessionApi, SessionAccount} from '#/state/session'
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {
|
||||
useClearPreferencesMutation,
|
||||
usePreferencesQuery,
|
||||
} from '#/state/queries/preferences'
|
||||
import {useClearPreferencesMutation} from '#/state/queries/preferences'
|
||||
// TODO import {useInviteCodesQuery} from '#/state/queries/invites'
|
||||
import {clear as clearStorage} from '#/state/persisted/store'
|
||||
import {clearLegacyStorage} from '#/state/persisted/legacy'
|
||||
|
@ -85,7 +82,11 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
|
|||
const contents = (
|
||||
<View style={[pal.view, styles.linkCard]}>
|
||||
<View style={styles.avi}>
|
||||
<UserAvatar size={40} avatar={profile?.avatar} />
|
||||
<UserAvatar
|
||||
size={40}
|
||||
avatar={profile?.avatar}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
<View style={[s.flex1]}>
|
||||
<Text type="md-bold" style={pal.text}>
|
||||
|
@ -156,7 +157,6 @@ export function SettingsScreen({}: Props) {
|
|||
const {screen, track} = useAnalytics()
|
||||
const {openModal} = useModalControls()
|
||||
const {isSwitchingAccounts, accounts, currentAccount} = useSession()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {mutate: clearPreferences} = useClearPreferencesMutation()
|
||||
// TODO
|
||||
// const {data: invites} = useInviteCodesQuery()
|
||||
|
@ -295,10 +295,7 @@ export function SettingsScreen({}: Props) {
|
|||
return (
|
||||
<View style={s.hContentRegion} testID="settingsScreen">
|
||||
<ExportCarDialog control={exportCarControl} />
|
||||
<BirthDateSettingsDialog
|
||||
control={birthdayControl}
|
||||
preferences={preferences}
|
||||
/>
|
||||
<BirthDateSettingsDialog control={birthdayControl} />
|
||||
|
||||
<SimpleViewHeader
|
||||
showBackButton={isMobile}
|
||||
|
@ -490,20 +487,20 @@ export function SettingsScreen({}: Props) {
|
|||
label={_(msg`System`)}
|
||||
left
|
||||
onSelect={() => setColorMode('system')}
|
||||
accessibilityHint={_(msg`Set color theme to system setting`)}
|
||||
accessibilityHint={_(msg`Sets color theme to system setting`)}
|
||||
/>
|
||||
<SelectableBtn
|
||||
selected={colorMode === 'light'}
|
||||
label={_(msg`Light`)}
|
||||
onSelect={() => setColorMode('light')}
|
||||
accessibilityHint={_(msg`Set color theme to light`)}
|
||||
accessibilityHint={_(msg`Sets color theme to light`)}
|
||||
/>
|
||||
<SelectableBtn
|
||||
selected={colorMode === 'dark'}
|
||||
label={_(msg`Dark`)}
|
||||
right
|
||||
onSelect={() => setColorMode('dark')}
|
||||
accessibilityHint={_(msg`Set color theme to dark`)}
|
||||
accessibilityHint={_(msg`Sets color theme to dark`)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -522,14 +519,14 @@ export function SettingsScreen({}: Props) {
|
|||
label={_(msg`Dim`)}
|
||||
left
|
||||
onSelect={() => setDarkTheme('dim')}
|
||||
accessibilityHint={_(msg`Set dark theme to the dim theme`)}
|
||||
accessibilityHint={_(msg`Sets dark theme to the dim theme`)}
|
||||
/>
|
||||
<SelectableBtn
|
||||
selected={darkTheme === 'dark'}
|
||||
label={_(msg`Dark`)}
|
||||
right
|
||||
onSelect={() => setDarkTheme('dark')}
|
||||
accessibilityHint={_(msg`Set dark theme to the dark theme`)}
|
||||
accessibilityHint={_(msg`Sets dark theme to the dark theme`)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -549,8 +546,8 @@ export function SettingsScreen({}: Props) {
|
|||
]}
|
||||
onPress={openFollowingFeedPreferences}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint=""
|
||||
accessibilityLabel={_(msg`Opens the home feed preferences`)}>
|
||||
accessibilityLabel={_(msg`Following feed preferences`)}
|
||||
accessibilityHint={_(msg`Opens the Following feed preferences`)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
icon="sliders"
|
||||
|
@ -570,8 +567,8 @@ export function SettingsScreen({}: Props) {
|
|||
]}
|
||||
onPress={openThreadsPreferences}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint=""
|
||||
accessibilityLabel={_(msg`Opens the threads preferences`)}>
|
||||
accessibilityLabel={_(msg`Thread preferences`)}
|
||||
accessibilityHint={_(msg`Opens the threads preferences`)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
icon={['far', 'comments']}
|
||||
|
@ -590,9 +587,10 @@ export function SettingsScreen({}: Props) {
|
|||
pal.view,
|
||||
isSwitchingAccounts && styles.dimmed,
|
||||
]}
|
||||
accessibilityHint="My Saved Feeds"
|
||||
accessibilityLabel={_(msg`Opens screen with all saved feeds`)}
|
||||
onPress={onPressSavedFeeds}>
|
||||
onPress={onPressSavedFeeds}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`My saved feeds`)}
|
||||
accessibilityHint={_(msg`Opens screen with all saved feeds`)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<HashtagIcon style={pal.text} size={18} strokeWidth={3} />
|
||||
</View>
|
||||
|
@ -691,7 +689,7 @@ export function SettingsScreen({}: Props) {
|
|||
onPress={onPressAppPasswords}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`App password settings`)}
|
||||
accessibilityHint={_(msg`Opens the app password settings page`)}>
|
||||
accessibilityHint={_(msg`Opens the app password settings`)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
icon="lock"
|
||||
|
@ -712,7 +710,9 @@ export function SettingsScreen({}: Props) {
|
|||
onPress={isSwitchingAccounts ? undefined : onPressChangeHandle}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Change handle`)}
|
||||
accessibilityHint={_(msg`Choose a new Bluesky username or create`)}>
|
||||
accessibilityHint={_(
|
||||
msg`Opens modal for choosing or creating a new Bluesky username`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
icon="at"
|
||||
|
@ -748,7 +748,9 @@ export function SettingsScreen({}: Props) {
|
|||
onPress={() => openModal({name: 'change-password'})}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Change password`)}
|
||||
accessibilityHint={_(msg`Change your Bluesky password`)}>
|
||||
accessibilityHint={_(
|
||||
msg`Opens modal for changing your Bluesky password`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
icon="lock"
|
||||
|
@ -770,7 +772,7 @@ export function SettingsScreen({}: Props) {
|
|||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Export my data`)}
|
||||
accessibilityHint={_(
|
||||
msg`Download Bluesky account data (repository)`,
|
||||
msg`Opens modal for downloading Bluesky account data (repository)`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
|
@ -789,7 +791,7 @@ export function SettingsScreen({}: Props) {
|
|||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Delete account`)}
|
||||
accessibilityHint={_(
|
||||
msg`Opens modal for account deletion confirmation. Requires email code.`,
|
||||
msg`Opens modal for account deletion confirmation. Requires email code`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, dangerBg]}>
|
||||
<FontAwesomeIcon
|
||||
|
@ -807,8 +809,8 @@ export function SettingsScreen({}: Props) {
|
|||
style={[pal.view, styles.linkCardNoIcon]}
|
||||
onPress={onPressSystemLog}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint="Open system log"
|
||||
accessibilityLabel={_(msg`Opens the system log page`)}>
|
||||
accessibilityLabel={_(msg`Open system log`)}
|
||||
accessibilityHint={_(msg`Opens the system log page`)}>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>System log</Trans>
|
||||
</Text>
|
||||
|
@ -839,7 +841,7 @@ export function SettingsScreen({}: Props) {
|
|||
style={[pal.view, styles.linkCardNoIcon]}
|
||||
onPress={onPressResetPreferences}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Reset preferences`)}
|
||||
accessibilityLabel={_(msg`Reset preferences state`)}
|
||||
accessibilityHint={_(msg`Resets the preferences state`)}>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>Reset preferences state</Trans>
|
||||
|
@ -849,7 +851,7 @@ export function SettingsScreen({}: Props) {
|
|||
style={[pal.view, styles.linkCardNoIcon]}
|
||||
onPress={onPressResetOnboarding}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Reset onboarding`)}
|
||||
accessibilityLabel={_(msg`Reset onboarding state`)}
|
||||
accessibilityHint={_(msg`Resets the onboarding state`)}>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>Reset onboarding state</Trans>
|
||||
|
@ -860,7 +862,7 @@ export function SettingsScreen({}: Props) {
|
|||
onPress={clearAllLegacyStorage}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Clear all legacy storage data`)}
|
||||
accessibilityHint={_(msg`Clear all legacy storage data`)}>
|
||||
accessibilityHint={_(msg`Clears all legacy storage data`)}>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>
|
||||
Clear all legacy storage data (restart after this)
|
||||
|
@ -872,7 +874,7 @@ export function SettingsScreen({}: Props) {
|
|||
onPress={clearAllStorage}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Clear all storage data`)}
|
||||
accessibilityHint={_(msg`Clear all storage data`)}>
|
||||
accessibilityHint={_(msg`Clears all storage data`)}>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>Clear all storage data (restart after this)</Trans>
|
||||
</Text>
|
||||
|
@ -961,7 +963,7 @@ function EmailConfirmationNotice() {
|
|||
]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Verify my email`)}
|
||||
accessibilityHint=""
|
||||
accessibilityHint={_(msg`Opens modal for email verification`)}
|
||||
onPress={() => openModal({name: 'verify-email'})}>
|
||||
<FontAwesomeIcon
|
||||
icon="envelope"
|
||||
|
|
|
@ -75,6 +75,7 @@ let DrawerProfileCard = ({
|
|||
avatar={profile?.avatar}
|
||||
// See https://github.com/bluesky-social/social-app/pull/1801:
|
||||
usePlainRNImage={true}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
<Text
|
||||
type="title-lg"
|
||||
|
@ -93,10 +94,12 @@ let DrawerProfileCard = ({
|
|||
{formatCountShortOnly(profile?.followersCount ?? 0)}
|
||||
</Text>{' '}
|
||||
{pluralize(profile?.followersCount || 0, 'follower')} ·{' '}
|
||||
<Text type="xl-medium" style={pal.text}>
|
||||
{formatCountShortOnly(profile?.followsCount ?? 0)}
|
||||
</Text>{' '}
|
||||
following
|
||||
<Trans>
|
||||
<Text type="xl-medium" style={pal.text}>
|
||||
{formatCountShortOnly(profile?.followsCount ?? 0)}
|
||||
</Text>{' '}
|
||||
following
|
||||
</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
|
|
@ -58,7 +58,7 @@ let NavSignupCard = ({}: {}): React.ReactNode => {
|
|||
accessibilityHint={_(msg`Sign in`)}
|
||||
accessibilityLabel={_(msg`Sign in`)}>
|
||||
<Text type="md" style={[pal.text, s.bold]}>
|
||||
Sign in
|
||||
<Trans>Sign in</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
|
|
|
@ -229,6 +229,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||
size={27}
|
||||
// See https://github.com/bluesky-social/social-app/pull/1801:
|
||||
usePlainRNImage={true}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
|
@ -238,6 +239,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||
size={28}
|
||||
// See https://github.com/bluesky-social/social-app/pull/1801:
|
||||
usePlainRNImage={true}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
|
|
@ -64,7 +64,11 @@ function ProfileCard() {
|
|||
style={[styles.profileCard, !isDesktop && styles.profileCardTablet]}
|
||||
title={_(msg`My Profile`)}
|
||||
asAnchor>
|
||||
<UserAvatar avatar={profile.avatar} size={size} />
|
||||
<UserAvatar
|
||||
avatar={profile.avatar}
|
||||
size={size}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
<View style={[styles.profileCard, !isDesktop && styles.profileCardTablet]}>
|
||||
|
|
|
@ -112,6 +112,7 @@ export function SearchProfileCard({
|
|||
size={40}
|
||||
avatar={profile.avatar}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={profile.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
<View style={{flex: 1}}>
|
||||
<Text
|
||||
|
|
|
@ -34,10 +34,10 @@
|
|||
jsonpointer "^5.0.0"
|
||||
leven "^3.1.0"
|
||||
|
||||
"@atproto/api@^0.12.0":
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.0.tgz#69e52f8761dc7d76c675fa7284bd49240bb0df64"
|
||||
integrity sha512-nSWiad1Z6IC/oVFSVxD5gZLhkD+J4EW2CFqAqIhklJNc0cjFKdmf8D56Pac6Ktm1sJoM6TVZ8GEeuEG6bJS/aQ==
|
||||
"@atproto/api@^0.12.1":
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.1.tgz#3340cbbd6a51a8c2f3248dae55a01415ab71084e"
|
||||
integrity sha512-Grigs9neuQxytXr2yHq/IfNlgXQVptWDO9KTQr5FDmgMY4Zly2X7Sa99u9c1CW9auwUTbcd+yRFBNEtbA3n3qg==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.3.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
|
|
Loading…
Reference in New Issue