Merge remote-tracking branch 'origin/main' into hailey/explicitly-filter

This commit is contained in:
Hailey
2024-06-24 17:02:40 -07:00
19 changed files with 527 additions and 434 deletions
+4 -1
View File
@@ -88,7 +88,10 @@ export function Outer({
if (!isOpen) return if (!isOpen) return
function handler(e: KeyboardEvent) { function handler(e: KeyboardEvent) {
if (e.key === 'Escape') close() if (e.key === 'Escape') {
e.stopPropagation()
close()
}
} }
document.addEventListener('keydown', handler) document.addEventListener('keydown', handler)
+34 -21
View File
@@ -6,10 +6,10 @@ import {useLingui} from '@lingui/react'
import {differenceInSeconds} from 'date-fns' import {differenceInSeconds} from 'date-fns'
import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
import {isNative} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {HITSLOP_10} from 'lib/constants' import {HITSLOP_10} from 'lib/constants'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
import {isWeb} from 'platform/detection'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
@@ -70,19 +70,27 @@ export function NewskieDialog({
<Dialog.ScrollableInner <Dialog.ScrollableInner
label={_(msg`New user info dialog`)} label={_(msg`New user info dialog`)}
style={[{width: 'auto', maxWidth: 400, minWidth: 200}]}> style={[{width: 'auto', maxWidth: 400, minWidth: 200}]}>
<View style={[a.gap_sm]}> <View style={[a.gap_md]}>
<View style={[a.align_center]}> <View style={[a.align_center]}>
<Newskie <View
width={64} style={[
height={64} {
fill="#FFC404" height: 60,
style={{marginTop: -10}} width: 64,
/> },
<Text style={[a.font_bold, a.text_xl, {marginTop: -10}]}> ]}>
<Newskie
width={64}
height={64}
fill="#FFC404"
style={[a.absolute, a.inset_0]}
/>
</View>
<Text style={[a.font_bold, a.text_xl]}>
<Trans>Say hello!</Trans> <Trans>Say hello!</Trans>
</Text> </Text>
</View> </View>
<Text style={[a.text_md, a.text_center, a.leading_tight]}> <Text style={[a.text_md, a.text_center, a.leading_snug]}>
{profile.joinedViaStarterPack ? ( {profile.joinedViaStarterPack ? (
<Trans> <Trans>
{profileName} joined Bluesky using a starter pack{' '} {profileName} joined Bluesky using a starter pack{' '}
@@ -116,18 +124,23 @@ export function NewskieDialog({
</View> </View>
</StarterPackCard.Link> </StarterPackCard.Link>
) : null} ) : null}
<Button
label={_(msg`Close`)} {isNative && (
variant="solid" <Button
color="secondary" label={_(msg`Close`)}
size="small" variant="solid"
style={[a.mt_sm, isWeb && [a.self_center, {marginLeft: 'auto'}]]} color="secondary"
onPress={() => control.close()}> size="small"
<ButtonText> style={[a.mt_sm]}
<Trans>Close</Trans> onPress={() => control.close()}>
</ButtonText> <ButtonText>
</Button> <Trans>Close</Trans>
</ButtonText>
</Button>
)}
</View> </View>
<Dialog.Close />
</Dialog.ScrollableInner> </Dialog.ScrollableInner>
</Dialog.Outer> </Dialog.Outer>
</View> </View>
+82 -68
View File
@@ -17,7 +17,6 @@ import {HITSLOP_10} from '#/lib/constants'
import {makeListLink, makeProfileLink} from '#/lib/routes/links' import {makeListLink, makeProfileLink} from '#/lib/routes/links'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useModalControls} from '#/state/modals'
import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread' import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread'
import { import {
ThreadgateSetting, ThreadgateSetting,
@@ -34,6 +33,7 @@ import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {TextLink} from '../view/com/util/Link' import {TextLink} from '../view/com/util/Link'
import {ThreadgateEditorDialog} from './dialogs/ThreadgateEditor'
import {PencilLine_Stroke2_Corner0_Rounded as PencilLine} from './icons/Pencil' import {PencilLine_Stroke2_Corner0_Rounded as PencilLine} from './icons/Pencil'
interface WhoCanReplyProps { interface WhoCanReplyProps {
@@ -46,7 +46,15 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const infoDialogControl = useDialogControl() const infoDialogControl = useDialogControl()
const {settings, isRootPost, onPressEdit} = useWhoCanReply(post) const editDialogControl = useDialogControl()
const agent = useAgent()
const queryClient = useQueryClient()
const settings = React.useMemo(
() => threadgateViewToSettings(post.threadgate),
[post],
)
const isRootPost = !('reply' in post.record)
if (!isRootPost) { if (!isRootPost) {
return null return null
@@ -63,6 +71,55 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
? _(msg`Replies disabled`) ? _(msg`Replies disabled`)
: _(msg`Some people can reply`) : _(msg`Some people can reply`)
const onPressEdit = () => {
if (isNative && Keyboard.isVisible()) {
Keyboard.dismiss()
}
if (isThreadAuthor) {
editDialogControl.open()
} else {
infoDialogControl.open()
}
}
const onEditConfirm = async (newSettings: ThreadgateSetting[]) => {
if (JSON.stringify(settings) === JSON.stringify(newSettings)) {
return
}
try {
if (newSettings.length) {
await createThreadgate(agent, post.uri, newSettings)
} else {
await agent.api.com.atproto.repo.deleteRecord({
repo: agent.session!.did,
collection: 'app.bsky.feed.threadgate',
rkey: new AtUri(post.uri).rkey,
})
}
await whenAppViewReady(agent, post.uri, res => {
const thread = res.data.thread
if (AppBskyFeedDefs.isThreadViewPost(thread)) {
const fetchedSettings = threadgateViewToSettings(
thread.post.threadgate,
)
return JSON.stringify(fetchedSettings) === JSON.stringify(newSettings)
}
return false
})
Toast.show(_(msg`Thread settings updated`))
queryClient.invalidateQueries({
queryKey: [POST_THREAD_RQKEY_ROOT],
})
} catch (err) {
Toast.show(
_(
msg`There was an issue. Please check your internet connection and try again.`,
),
)
logger.error('Failed to edit threadgate', {message: err})
}
}
return ( return (
<> <>
<Button <Button
@@ -93,7 +150,18 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
</View> </View>
)} )}
</Button> </Button>
<WhoCanReplyDialog control={infoDialogControl} post={post} /> <WhoCanReplyDialog
control={infoDialogControl}
post={post}
settings={settings}
/>
{isThreadAuthor && (
<ThreadgateEditorDialog
control={editDialogControl}
threadgate={settings}
onConfirm={onEditConfirm}
/>
)}
</> </>
) )
} }
@@ -113,24 +181,31 @@ function Icon({
return <IconComponent fill={color} width={width} /> return <IconComponent fill={color} width={width} />
} }
export function WhoCanReplyDialog({ function WhoCanReplyDialog({
control, control,
post, post,
settings,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
post: AppBskyFeedDefs.PostView post: AppBskyFeedDefs.PostView
settings: ThreadgateSetting[]
}) { }) {
return ( return (
<Dialog.Outer control={control}> <Dialog.Outer control={control}>
<Dialog.Handle /> <Dialog.Handle />
<WhoCanReplyDialogInner post={post} /> <WhoCanReplyDialogInner post={post} settings={settings} />
</Dialog.Outer> </Dialog.Outer>
) )
} }
function WhoCanReplyDialogInner({post}: {post: AppBskyFeedDefs.PostView}) { function WhoCanReplyDialogInner({
post,
settings,
}: {
post: AppBskyFeedDefs.PostView
settings: ThreadgateSetting[]
}) {
const {_} = useLingui() const {_} = useLingui()
const {settings} = useWhoCanReply(post)
return ( return (
<Dialog.ScrollableInner <Dialog.ScrollableInner
label={_(msg`Who can reply dialog`)} label={_(msg`Who can reply dialog`)}
@@ -245,67 +320,6 @@ function Separator({i, length}: {i: number; length: number}) {
return <>, </> return <>, </>
} }
function useWhoCanReply(post: AppBskyFeedDefs.PostView) {
const agent = useAgent()
const queryClient = useQueryClient()
const {openModal} = useModalControls()
const settings = React.useMemo(
() => threadgateViewToSettings(post.threadgate),
[post],
)
const isRootPost = !('reply' in post.record)
const onPressEdit = () => {
if (isNative && Keyboard.isVisible()) {
Keyboard.dismiss()
}
openModal({
name: 'threadgate',
settings,
async onConfirm(newSettings: ThreadgateSetting[]) {
if (JSON.stringify(settings) === JSON.stringify(newSettings)) {
return
}
try {
if (newSettings.length) {
await createThreadgate(agent, post.uri, newSettings)
} else {
await agent.api.com.atproto.repo.deleteRecord({
repo: agent.session!.did,
collection: 'app.bsky.feed.threadgate',
rkey: new AtUri(post.uri).rkey,
})
}
await whenAppViewReady(agent, post.uri, res => {
const thread = res.data.thread
if (AppBskyFeedDefs.isThreadViewPost(thread)) {
const fetchedSettings = threadgateViewToSettings(
thread.post.threadgate,
)
return (
JSON.stringify(fetchedSettings) === JSON.stringify(newSettings)
)
}
return false
})
Toast.show('Thread settings updated')
queryClient.invalidateQueries({
queryKey: [POST_THREAD_RQKEY_ROOT],
})
} catch (err) {
Toast.show(
'There was an issue. Please check your internet connection and try again.',
)
logger.error('Failed to edit threadgate', {message: err})
}
},
})
}
return {settings, isRootPost, onPressEdit}
}
async function whenAppViewReady( async function whenAppViewReady(
agent: BskyAgent, agent: BskyAgent,
uri: string, uri: string,
+1
View File
@@ -113,6 +113,7 @@ export function EmbedConsentDialog({
</ButtonText> </ButtonText>
</Button> </Button>
</View> </View>
<Dialog.Close />
</Dialog.ScrollableInner> </Dialog.ScrollableInner>
</Dialog.Outer> </Dialog.Outer>
) )
+218
View File
@@ -0,0 +1,218 @@
import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import isEqual from 'lodash.isequal'
import {useMyListsQuery} from '#/state/queries/my-lists'
import {ThreadgateSetting} from '#/state/queries/threadgate'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {Text} from '#/components/Typography'
interface ThreadgateEditorDialogProps {
control: Dialog.DialogControlProps
threadgate: ThreadgateSetting[]
onChange?: (v: ThreadgateSetting[]) => void
onConfirm?: (v: ThreadgateSetting[]) => void
}
export function ThreadgateEditorDialog({
control,
threadgate,
onChange,
onConfirm,
}: ThreadgateEditorDialogProps) {
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<DialogContent
seedThreadgate={threadgate}
onChange={onChange}
onConfirm={onConfirm}
/>
</Dialog.Outer>
)
}
function DialogContent({
seedThreadgate,
onChange,
onConfirm,
}: {
seedThreadgate: ThreadgateSetting[]
onChange?: (v: ThreadgateSetting[]) => void
onConfirm?: (v: ThreadgateSetting[]) => void
}) {
const {_} = useLingui()
const control = Dialog.useDialogContext()
const {data: lists} = useMyListsQuery('curate')
const [draft, setDraft] = React.useState(seedThreadgate)
const [prevSeedThreadgate, setPrevSeedThreadgate] =
React.useState(seedThreadgate)
if (seedThreadgate !== prevSeedThreadgate) {
// New data flowed from above (e.g. due to update coming through).
setPrevSeedThreadgate(seedThreadgate)
setDraft(seedThreadgate) // Reset draft.
}
function updateThreadgate(nextThreadgate: ThreadgateSetting[]) {
setDraft(nextThreadgate)
onChange?.(nextThreadgate)
}
const onPressEverybody = () => {
updateThreadgate([])
}
const onPressNobody = () => {
updateThreadgate([{type: 'nobody'}])
}
const onPressAudience = (setting: ThreadgateSetting) => {
// remove nobody
let newSelected = draft.filter(v => v.type !== 'nobody')
// toggle
const i = newSelected.findIndex(v => isEqual(v, setting))
if (i === -1) {
newSelected.push(setting)
} else {
newSelected.splice(i, 1)
}
updateThreadgate(newSelected)
}
const doneLabel = onConfirm ? _(msg`Save`) : _(msg`Done`)
return (
<Dialog.ScrollableInner
label={_(msg`Choose who can reply`)}
style={[{maxWidth: 500}, a.w_full]}>
<View style={[a.flex_1, a.gap_md]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Trans>Chose who can reply</Trans>
</Text>
<Text style={a.mt_xs}>
<Trans>Either choose "Everybody" or "Nobody"</Trans>
</Text>
<View style={[a.flex_row, a.gap_sm]}>
<Selectable
label={_(msg`Everybody`)}
isSelected={draft.length === 0}
onPress={onPressEverybody}
style={{flex: 1}}
/>
<Selectable
label={_(msg`Nobody`)}
isSelected={!!draft.find(v => v.type === 'nobody')}
onPress={onPressNobody}
style={{flex: 1}}
/>
</View>
<Text style={a.mt_md}>
<Trans>Or combine these options:</Trans>
</Text>
<View style={[a.gap_sm]}>
<Selectable
label={_(msg`Mentioned users`)}
isSelected={!!draft.find(v => v.type === 'mention')}
onPress={() => onPressAudience({type: 'mention'})}
/>
<Selectable
label={_(msg`Followed users`)}
isSelected={!!draft.find(v => v.type === 'following')}
onPress={() => onPressAudience({type: 'following'})}
/>
{lists && lists.length > 0
? lists.map(list => (
<Selectable
key={list.uri}
label={_(msg`Users in "${list.name}"`)}
isSelected={
!!draft.find(v => v.type === 'list' && v.list === list.uri)
}
onPress={() =>
onPressAudience({type: 'list', list: list.uri})
}
/>
))
: // No loading states to avoid jumps for the common case (no lists)
null}
</View>
</View>
<Button
label={doneLabel}
onPress={() => {
control.close()
onConfirm?.(draft)
}}
onAccessibilityEscape={control.close}
color="primary"
size="medium"
variant="solid"
style={a.mt_xl}>
<ButtonText>{doneLabel}</ButtonText>
</Button>
<Dialog.Close />
</Dialog.ScrollableInner>
)
}
function Selectable({
label,
isSelected,
onPress,
style,
}: {
label: string
isSelected: boolean
onPress: () => void
style?: StyleProp<ViewStyle>
}) {
const t = useTheme()
return (
<Button
onPress={onPress}
label={label}
accessibilityHint="Select this option"
accessibilityRole="checkbox"
aria-checked={isSelected}
accessibilityState={{
checked: isSelected,
}}
style={a.flex_1}>
{({hovered, focused}) => (
<View
style={[
a.flex_1,
a.flex_row,
a.align_center,
a.justify_between,
a.rounded_sm,
a.p_md,
{height: 40}, // for consistency with checkmark icon visible or not
t.atoms.bg_contrast_50,
(hovered || focused) && t.atoms.bg_contrast_100,
isSelected && {
backgroundColor:
t.name === 'light'
? t.palette.primary_50
: t.palette.primary_975,
},
style,
]}>
<Text style={[a.text_sm, isSelected && a.font_semibold]}>
{label}
</Text>
{isSelected ? (
<Check size="sm" fill={t.palette.primary_500} />
) : (
<View />
)}
</View>
)}
</Button>
)
}
+9
View File
@@ -13,6 +13,15 @@ export const EMBED_SERVICE = 'https://embed.bsky.app'
export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download'
// HACK
// Yes, this is exactly what it looks like. It's a hard-coded constant
// reflecting the number of new users in the last week. We don't have
// time to add a route to the servers for this so we're just going to hard
// code and update this number with each release until we can get the
// server route done.
// -prf
export const JOINED_THIS_WEEK = 37115 // as of June24 2024
const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new` const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
export function FEEDBACK_FORM_URL({ export function FEEDBACK_FORM_URL({
email, email,
+44 -44
View File
@@ -18,7 +18,7 @@ msgstr ""
#: src/screens/Messages/List/ChatListItem.tsx:120 #: src/screens/Messages/List/ChatListItem.tsx:120
msgid "(contains embedded content)" msgid "(contains embedded content)"
msgstr "" msgstr "(té contingut incrustat)"
#: src/view/com/modals/VerifyEmail.tsx:150 #: src/view/com/modals/VerifyEmail.tsx:150
msgid "(no email)" msgid "(no email)"
@@ -113,7 +113,7 @@ msgstr ""
#: src/view/com/util/UserAvatar.tsx:419 #: src/view/com/util/UserAvatar.tsx:419
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "" msgstr "Avatar de {0}"
#: src/screens/StarterPack/Wizard/StepDetails.tsx:68 #: src/screens/StarterPack/Wizard/StepDetails.tsx:68
msgid "{0}'s favorite feeds and people - join me!" msgid "{0}'s favorite feeds and people - join me!"
@@ -505,7 +505,7 @@ msgstr "Tots els canals que has desat, en un sol lloc."
#: src/view/com/modals/AddAppPasswords.tsx:187 #: src/view/com/modals/AddAppPasswords.tsx:187
#: src/view/com/modals/AddAppPasswords.tsx:194 #: src/view/com/modals/AddAppPasswords.tsx:194
msgid "Allow access to your direct messages" msgid "Allow access to your direct messages"
msgstr "" msgstr "Permet l'accés als teus missatges directes"
#: src/screens/Messages/Settings.tsx:61 #: src/screens/Messages/Settings.tsx:61
#: src/screens/Messages/Settings.tsx:64 #: src/screens/Messages/Settings.tsx:64
@@ -515,7 +515,7 @@ msgstr ""
#: src/screens/Messages/Settings.tsx:62 #: src/screens/Messages/Settings.tsx:62
#: src/screens/Messages/Settings.tsx:65 #: src/screens/Messages/Settings.tsx:65
msgid "Allow new messages from" msgid "Allow new messages from"
msgstr "" msgstr "Permet missatges nou de"
#: src/screens/Login/ForgotPasswordForm.tsx:178 #: src/screens/Login/ForgotPasswordForm.tsx:178
#: src/view/com/modals/ChangePassword.tsx:171 #: src/view/com/modals/ChangePassword.tsx:171
@@ -1039,7 +1039,7 @@ msgstr "Cancel·la la citació de la publicació"
#: src/screens/Deactivated.tsx:155 #: src/screens/Deactivated.tsx:155
msgid "Cancel reactivation and log out" msgid "Cancel reactivation and log out"
msgstr "" msgstr "Cancel·la la reactivació i surt"
#: src/view/com/modals/ListAddRemoveUsers.tsx:87 #: src/view/com/modals/ListAddRemoveUsers.tsx:87
#: src/view/shell/desktop/Search.tsx:214 #: src/view/shell/desktop/Search.tsx:214
@@ -1118,7 +1118,7 @@ msgstr "Configuració del xat"
#: src/screens/Messages/Settings.tsx:59 #: src/screens/Messages/Settings.tsx:59
#: src/view/screens/Settings/index.tsx:647 #: src/view/screens/Settings/index.tsx:647
msgid "Chat Settings" msgid "Chat Settings"
msgstr "" msgstr "Configuració del xat"
#: src/components/dms/ConvoMenu.tsx:84 #: src/components/dms/ConvoMenu.tsx:84
msgid "Chat unmuted" msgid "Chat unmuted"
@@ -1221,11 +1221,11 @@ msgstr "clica aquí"
#: src/view/com/modals/DeleteAccount.tsx:208 #: src/view/com/modals/DeleteAccount.tsx:208
msgid "Click here for more information on deactivating your account" msgid "Click here for more information on deactivating your account"
msgstr "" msgstr "Clica aquí per a més informació sobre desactivar el teu compte"
#: src/view/com/modals/DeleteAccount.tsx:216 #: src/view/com/modals/DeleteAccount.tsx:216
msgid "Click here for more information." msgid "Click here for more information."
msgstr "" msgstr "Clica aquí per a més informació."
#: src/screens/Feeds/NoFollowingFeed.tsx:46 #: src/screens/Feeds/NoFollowingFeed.tsx:46
#~ msgid "Click here to add one." #~ msgid "Click here to add one."
@@ -1325,7 +1325,7 @@ msgstr "Tanca la visualització de la imatge de la capçalera"
#: src/view/com/notifications/FeedItem.tsx:226 #: src/view/com/notifications/FeedItem.tsx:226
msgid "Collapse list of users" msgid "Collapse list of users"
msgstr "" msgstr "Plega la llista d'usuaris"
#: src/view/com/notifications/FeedItem.tsx:426 #: src/view/com/notifications/FeedItem.tsx:426
msgid "Collapses list of users for a given notification" msgid "Collapses list of users for a given notification"
@@ -1744,11 +1744,11 @@ msgstr "Data de naixement"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:73 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:73
#: src/view/screens/Settings/index.tsx:806 #: src/view/screens/Settings/index.tsx:806
msgid "Deactivate account" msgid "Deactivate account"
msgstr "" msgstr "Desactiva el compte"
#: src/view/screens/Settings/index.tsx:818 #: src/view/screens/Settings/index.tsx:818
msgid "Deactivate my account" msgid "Deactivate my account"
msgstr "" msgstr "Desactiva el meu compte"
#: src/view/screens/Settings/index.tsx:873 #: src/view/screens/Settings/index.tsx:873
msgid "Debug Moderation" msgid "Debug Moderation"
@@ -2402,7 +2402,7 @@ msgstr "Expandeix el text alternatiu"
#: src/view/com/notifications/FeedItem.tsx:227 #: src/view/com/notifications/FeedItem.tsx:227
msgid "Expand list of users" msgid "Expand list of users"
msgstr "" msgstr "Expandeix la llista d'usuaris"
#: src/view/com/composer/ComposerReplyTo.tsx:82 #: src/view/com/composer/ComposerReplyTo.tsx:82
#: src/view/com/composer/ComposerReplyTo.tsx:85 #: src/view/com/composer/ComposerReplyTo.tsx:85
@@ -2679,7 +2679,7 @@ msgstr "Segueix {0}"
#: src/view/com/posts/AviFollowButton.tsx:71 #: src/view/com/posts/AviFollowButton.tsx:71
msgid "Follow {name}" msgid "Follow {name}"
msgstr "" msgstr "Segueix a {name}"
#: src/view/com/profile/ProfileMenu.tsx:246 #: src/view/com/profile/ProfileMenu.tsx:246
#: src/view/com/profile/ProfileMenu.tsx:257 #: src/view/com/profile/ProfileMenu.tsx:257
@@ -2782,7 +2782,7 @@ msgstr "Seguint {0}"
#: src/view/com/posts/AviFollowButton.tsx:53 #: src/view/com/posts/AviFollowButton.tsx:53
msgid "Following {name}" msgid "Following {name}"
msgstr "" msgstr "Seguint a {name}"
#: src/view/screens/Settings/index.tsx:573 #: src/view/screens/Settings/index.tsx:573
msgid "Following feed preferences" msgid "Following feed preferences"
@@ -3145,7 +3145,7 @@ msgstr "Si vols canviar la contrasenya t'enviarem un codi per a verificar que aq
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:92 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:92
msgid "If you're trying to change your handle or email, do so before you deactivate." msgid "If you're trying to change your handle or email, do so before you deactivate."
msgstr "" msgstr "Si vols canviar el teu identificador o el correu fes-ho abans de desactivar el compte."
#: src/lib/moderation/useReportOptions.ts:38 #: src/lib/moderation/useReportOptions.ts:38
msgid "Illegal and Urgent" msgid "Illegal and Urgent"
@@ -3622,7 +3622,7 @@ msgstr "Registre"
#: src/screens/Deactivated.tsx:214 #: src/screens/Deactivated.tsx:214
#: src/screens/Deactivated.tsx:220 #: src/screens/Deactivated.tsx:220
msgid "Log in or sign up" msgid "Log in or sign up"
msgstr "" msgstr "Inicia sessió o registra't"
#: src/screens/SignupQueued.tsx:155 #: src/screens/SignupQueued.tsx:155
#: src/screens/SignupQueued.tsx:158 #: src/screens/SignupQueued.tsx:158
@@ -4380,7 +4380,7 @@ msgstr "Obre"
#: src/view/com/posts/AviFollowButton.tsx:89 #: src/view/com/posts/AviFollowButton.tsx:89
msgid "Open {name} profile shortcut menu" msgid "Open {name} profile shortcut menu"
msgstr "" msgstr "Obre el menú de drecera del perfil {name}"
#: src/screens/Onboarding/StepProfile/index.tsx:277 #: src/screens/Onboarding/StepProfile/index.tsx:277
msgid "Open avatar creator" msgid "Open avatar creator"
@@ -4463,7 +4463,7 @@ msgstr "Obre la càmera del dispositiu"
#: src/view/screens/Settings/index.tsx:639 #: src/view/screens/Settings/index.tsx:639
msgid "Opens chat settings" msgid "Opens chat settings"
msgstr "" msgstr "Obre la configuració del xat"
#: src/view/com/composer/Prompt.tsx:27 #: src/view/com/composer/Prompt.tsx:27
msgid "Opens composer" msgid "Opens composer"
@@ -4517,7 +4517,7 @@ msgstr "Obre la llista de codis d'invitació"
#: src/view/screens/Settings/index.tsx:808 #: src/view/screens/Settings/index.tsx:808
msgid "Opens modal for account deactivation confirmation" msgid "Opens modal for account deactivation confirmation"
msgstr "" msgstr "Obre el modal per a la confirmació de la desactivació del compte"
#: src/view/screens/Settings/index.tsx:830 #: src/view/screens/Settings/index.tsx:830
msgid "Opens modal for account deletion confirmation. Requires email code" msgid "Opens modal for account deletion confirmation. Requires email code"
@@ -4604,7 +4604,7 @@ msgstr "Obre les preferències dels fils de debat"
#: src/view/com/notifications/FeedItem.tsx:513 #: src/view/com/notifications/FeedItem.tsx:513
#: src/view/com/util/UserAvatar.tsx:422 #: src/view/com/util/UserAvatar.tsx:422
msgid "Opens this profile" msgid "Opens this profile"
msgstr "" msgstr "Obre aquest perfil"
#: src/view/com/util/forms/DropdownButton.tsx:293 #: src/view/com/util/forms/DropdownButton.tsx:293
msgid "Option {0} of {numItems}" msgid "Option {0} of {numItems}"
@@ -4621,11 +4621,11 @@ msgstr "O combina aquestes opcions:"
#: src/screens/Deactivated.tsx:211 #: src/screens/Deactivated.tsx:211
msgid "Or, continue with another account." msgid "Or, continue with another account."
msgstr "" msgstr "O continua amb un altre compte."
#: src/screens/Deactivated.tsx:194 #: src/screens/Deactivated.tsx:194
msgid "Or, log into one of your other accounts." msgid "Or, log into one of your other accounts."
msgstr "" msgstr "O inicia sessió en un altre dels teus comptes."
#: src/lib/moderation/useReportOptions.ts:27 #: src/lib/moderation/useReportOptions.ts:27
msgid "Other" msgid "Other"
@@ -5067,7 +5067,7 @@ msgstr "Proporcions"
#: src/screens/Deactivated.tsx:144 #: src/screens/Deactivated.tsx:144
msgid "Reactivate your account" msgid "Reactivate your account"
msgstr "" msgstr "Torna a activar el teu compte"
#: src/components/dms/ReportDialog.tsx:174 #: src/components/dms/ReportDialog.tsx:174
msgid "Reason:" msgid "Reason:"
@@ -5129,7 +5129,7 @@ msgstr "Elimina el bàner"
#: src/screens/Messages/Conversation/MessageInputEmbed.tsx:218 #: src/screens/Messages/Conversation/MessageInputEmbed.tsx:218
msgid "Remove embed" msgid "Remove embed"
msgstr "" msgstr "Elimina l'incrustat"
#: src/view/com/posts/FeedErrorMessage.tsx:168 #: src/view/com/posts/FeedErrorMessage.tsx:168
#: src/view/com/posts/FeedShutdownMsg.tsx:113 #: src/view/com/posts/FeedShutdownMsg.tsx:113
@@ -5168,11 +5168,11 @@ msgstr "Elimina la paraula silenciada de la teva llista"
#: src/view/screens/Search/Search.tsx:974 #: src/view/screens/Search/Search.tsx:974
msgid "Remove profile" msgid "Remove profile"
msgstr "" msgstr "Elimina el perfil"
#: src/view/screens/Search/Search.tsx:976 #: src/view/screens/Search/Search.tsx:976
msgid "Remove profile from search history" msgid "Remove profile from search history"
msgstr "" msgstr "Elimina el perfil de l'historial de cerca"
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:238 #: src/view/com/util/post-embeds/QuoteEmbed.tsx:238
msgid "Remove quote" msgid "Remove quote"
@@ -5868,7 +5868,7 @@ msgstr "Envia el missatge"
#: src/components/dms/dialogs/ShareViaChatDialog.tsx:59 #: src/components/dms/dialogs/ShareViaChatDialog.tsx:59
msgid "Send post to..." msgid "Send post to..."
msgstr "" msgstr "Envia el missatge a..."
#: src/components/dms/ReportDialog.tsx:234 #: src/components/dms/ReportDialog.tsx:234
#: src/components/dms/ReportDialog.tsx:237 #: src/components/dms/ReportDialog.tsx:237
@@ -5893,7 +5893,7 @@ msgstr "Envia un correu de verificació"
#: src/view/com/util/forms/PostDropdownBtn.tsx:296 #: src/view/com/util/forms/PostDropdownBtn.tsx:296
#: src/view/com/util/forms/PostDropdownBtn.tsx:299 #: src/view/com/util/forms/PostDropdownBtn.tsx:299
msgid "Send via direct message" msgid "Send via direct message"
msgstr "" msgstr "Envia per missatge directe"
#: src/view/com/modals/DeleteAccount.tsx:151 #: src/view/com/modals/DeleteAccount.tsx:151
msgid "Sends email with confirmation code for account deletion" msgid "Sends email with confirmation code for account deletion"
@@ -6143,7 +6143,7 @@ msgstr "Mostra seguidors semblants a {0}"
#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23 #: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23
msgid "Show hidden replies" msgid "Show hidden replies"
msgstr "" msgstr "Mostra les respostes ocultes"
#: src/view/com/util/forms/PostDropdownBtn.tsx:346 #: src/view/com/util/forms/PostDropdownBtn.tsx:346
#: src/view/com/util/forms/PostDropdownBtn.tsx:348 #: src/view/com/util/forms/PostDropdownBtn.tsx:348
@@ -6163,7 +6163,7 @@ msgstr "Mostra'n més com aquest"
#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23 #: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23
msgid "Show muted replies" msgid "Show muted replies"
msgstr "" msgstr "Mostra les respostes silenciades"
#: src/view/screens/PreferencesFollowingFeed.tsx:257 #: src/view/screens/PreferencesFollowingFeed.tsx:257
msgid "Show Posts from My Feeds" msgid "Show Posts from My Feeds"
@@ -6371,7 +6371,7 @@ msgstr "Alguna cosa ha fallat"
#: src/screens/Deactivated.tsx:94 #: src/screens/Deactivated.tsx:94
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:59 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:59
msgid "Something went wrong, please try again" msgid "Something went wrong, please try again"
msgstr "" msgstr "Alguna cosa ha fallat, torna-ho a provar"
#: src/components/ReportDialog/index.tsx:59 #: src/components/ReportDialog/index.tsx:59
#: src/screens/Moderation/index.tsx:114 #: src/screens/Moderation/index.tsx:114
@@ -6715,7 +6715,7 @@ msgstr "Les condicions del servei han estat traslladades a"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:86 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:86
msgid "There is no time limit for account deactivation, come back any time." msgid "There is no time limit for account deactivation, come back any time."
msgstr "" msgstr "No hi ha límit de temps per a la desactivació del compte, torna quan vulguis."
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:115 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:115
#: src/view/screens/ProfileFeed.tsx:542 #: src/view/screens/ProfileFeed.tsx:542
@@ -7517,7 +7517,7 @@ msgstr "Veure l'avatar de {0}"
#: src/view/com/notifications/FeedItem.tsx:234 #: src/view/com/notifications/FeedItem.tsx:234
msgid "View {0}'s profile" msgid "View {0}'s profile"
msgstr "" msgstr "Veure el perfil de {0}"
#: src/components/ProfileHoverCard/index.web.tsx:430 #: src/components/ProfileHoverCard/index.web.tsx:430
msgid "View blocked user's profile" msgid "View blocked user's profile"
@@ -7682,7 +7682,7 @@ msgstr ""
#: src/screens/Deactivated.tsx:128 #: src/screens/Deactivated.tsx:128
msgid "Welcome back!" msgid "Welcome back!"
msgstr "" msgstr "Bentornat!"
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:48 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:48
#~ msgid "Welcome to <0>Bluesky</0>" #~ msgid "Welcome to <0>Bluesky</0>"
@@ -7808,7 +7808,7 @@ msgstr "Sí"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:106 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:106
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:108 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:108
msgid "Yes, deactivate" msgid "Yes, deactivate"
msgstr "" msgstr "Sí, desactiva'l"
#: src/screens/StarterPack/StarterPackScreen.tsx:525 #: src/screens/StarterPack/StarterPackScreen.tsx:525
msgid "Yes, delete this starter pack" msgid "Yes, delete this starter pack"
@@ -7816,7 +7816,7 @@ msgstr ""
#: src/screens/Deactivated.tsx:150 #: src/screens/Deactivated.tsx:150
msgid "Yes, reactivate my account" msgid "Yes, reactivate my account"
msgstr "" msgstr "Sí, torna a activar el meu compte"
#: src/components/dms/MessageItem.tsx:188 #: src/components/dms/MessageItem.tsx:188
msgid "Yesterday, {time}" msgid "Yesterday, {time}"
@@ -7841,7 +7841,7 @@ msgstr "També pots descobrir nous canals personalitzats per a seguir."
#: src/view/com/modals/DeleteAccount.tsx:202 #: src/view/com/modals/DeleteAccount.tsx:202
msgid "You can also temporarily deactivate your account instead, and reactivate it at any time." msgid "You can also temporarily deactivate your account instead, and reactivate it at any time."
msgstr "" msgstr "També pots desactivar el teu compte temporalment i reactivar-lo en qualsevol moment."
#: src/view/com/auth/create/Step1.tsx:106 #: src/view/com/auth/create/Step1.tsx:106
#~ msgid "You can change hosting providers at any time." #~ msgid "You can change hosting providers at any time."
@@ -7857,7 +7857,7 @@ msgstr "Pots canviar-ho quan vulguis."
#: src/screens/Messages/Settings.tsx:111 #: src/screens/Messages/Settings.tsx:111
msgid "You can continue ongoing conversations regardless of which setting you choose." msgid "You can continue ongoing conversations regardless of which setting you choose."
msgstr "" msgstr "Pots continuar les converses en curs independentment de la configuració que triïs."
#: src/screens/Login/index.tsx:158 #: src/screens/Login/index.tsx:158
#: src/screens/Login/PasswordUpdatedForm.tsx:33 #: src/screens/Login/PasswordUpdatedForm.tsx:33
@@ -7866,7 +7866,7 @@ msgstr "Ara pots iniciar sessió amb la nova contrasenya."
#: src/screens/Deactivated.tsx:136 #: src/screens/Deactivated.tsx:136
msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users." msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users."
msgstr "" msgstr "Pots reactivar el teu compte per continuar iniciant la sessió. El teu perfil i les publicacions seran visibles per a altres usuaris."
#: src/view/com/profile/ProfileFollowers.tsx:86 #: src/view/com/profile/ProfileFollowers.tsx:86
msgid "You do not have any followers." msgid "You do not have any followers."
@@ -8025,7 +8025,7 @@ msgstr "Has d'escollir almenys un etiquetador per a un informe"
#: src/screens/Deactivated.tsx:131 #: src/screens/Deactivated.tsx:131
msgid "You previously deactivated @{0}." msgid "You previously deactivated @{0}."
msgstr "" msgstr "Abans has desactivat @{0}."
#: src/view/com/util/forms/PostDropdownBtn.tsx:174 #: src/view/com/util/forms/PostDropdownBtn.tsx:174
msgid "You will no longer receive notifications for this thread" msgid "You will no longer receive notifications for this thread"
@@ -8045,11 +8045,11 @@ msgstr "Tu: {0}"
#: src/screens/Messages/List/ChatListItem.tsx:143 #: src/screens/Messages/List/ChatListItem.tsx:143
msgid "You: {defaultEmbeddedContentMessage}" msgid "You: {defaultEmbeddedContentMessage}"
msgstr "" msgstr "Tu: {defaultEmbeddedContentMessage}"
#: src/screens/Messages/List/ChatListItem.tsx:136 #: src/screens/Messages/List/ChatListItem.tsx:136
msgid "You: {short}" msgid "You: {short}"
msgstr "" msgstr "Tu: {short}"
#: src/screens/Signup/index.tsx:169 #: src/screens/Signup/index.tsx:169
msgid "You'll follow the suggested users and feeds once you finish creating your account!" msgid "You'll follow the suggested users and feeds once you finish creating your account!"
@@ -8084,7 +8084,7 @@ msgstr "Estàs a la cua"
#: src/screens/Deactivated.tsx:89 #: src/screens/Deactivated.tsx:89
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:54 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:54
msgid "You're logged in with an App Password. Please log in with your main password to continue deactivating your account." msgid "You're logged in with an App Password. Please log in with your main password to continue deactivating your account."
msgstr "" msgstr "Has iniciat sessió amb una contrasenya d'aplicació. Inicia sessió amb la teva contrasenya principal per continuar la desactivació del teu compte."
#: src/screens/Onboarding/StepFinished.tsx:228 #: src/screens/Onboarding/StepFinished.tsx:228
msgid "You're ready to go!" msgid "You're ready to go!"
@@ -8189,7 +8189,7 @@ msgstr "El teu perfil"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:75 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:75
msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in."
msgstr "" msgstr "El teu perfil, publicacions, fonts i llistes ja no seran visibles per a altres usuaris de Bluesky. Pots reactivar el teu compte en qualsevol moment iniciant sessió."
#: src/view/com/composer/Composer.tsx:365 #: src/view/com/composer/Composer.tsx:365
msgid "Your reply has been published" msgid "Your reply has been published"
+2 -2
View File
@@ -110,8 +110,8 @@ export function StepFinished() {
// Any starter pack feeds will be pinned _after_ the defaults // Any starter pack feeds will be pinned _after_ the defaults
if (starterPack && starterPack.feeds?.length) { if (starterPack && starterPack.feeds?.length) {
feedsToSave.concat( feedsToSave.push(
starterPack.feeds.map(f => ({ ...starterPack.feeds.map(f => ({
type: 'feed', type: 'feed',
value: f.uri, value: f.uri,
pinned: true, pinned: true,
+2 -2
View File
@@ -5,7 +5,7 @@ import {Trans} from '@lingui/macro'
import {Shadow} from '#/state/cache/types' import {Shadow} from '#/state/cache/types'
import {isInvalidHandle} from 'lib/strings/handles' import {isInvalidHandle} from 'lib/strings/handles'
import {isAndroid} from 'platform/detection' import {isIOS} from 'platform/detection'
import {atoms as a, useTheme, web} from '#/alf' import {atoms as a, useTheme, web} from '#/alf'
import {NewskieDialog} from '#/components/NewskieDialog' import {NewskieDialog} from '#/components/NewskieDialog'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -23,7 +23,7 @@ export function ProfileHeaderHandle({
return ( return (
<View <View
style={[a.flex_row, a.gap_xs, a.align_center]} style={[a.flex_row, a.gap_xs, a.align_center]}
pointerEvents={disableTaps ? 'none' : isAndroid ? 'box-only' : 'auto'}> pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}>
<NewskieDialog profile={profile} disabled={disableTaps} /> <NewskieDialog profile={profile} disabled={disableTaps} />
{profile.viewer?.followedBy && !blockHide ? ( {profile.viewer?.followedBy && !blockHide ? (
<View style={[t.atoms.bg_contrast_25, a.rounded_xs, a.px_sm, a.py_xs]}> <View style={[t.atoms.bg_contrast_25, a.rounded_xs, a.px_sm, a.py_xs]}>
@@ -11,6 +11,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {JOINED_THIS_WEEK} from '#/lib/constants'
import {isAndroidWeb} from 'lib/browser' import {isAndroidWeb} from 'lib/browser'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {createStarterPackGooglePlayUri} from 'lib/strings/starter-pack' import {createStarterPackGooglePlayUri} from 'lib/strings/starter-pack'
@@ -21,6 +22,7 @@ import {
useActiveStarterPack, useActiveStarterPack,
useSetActiveStarterPack, useSetActiveStarterPack,
} from 'state/shell/starter-pack' } from 'state/shell/starter-pack'
import {formatCount} from '#/view/com/util/numeric/format'
import {LoggedOutScreenState} from 'view/com/auth/LoggedOut' import {LoggedOutScreenState} from 'view/com/auth/LoggedOut'
import {CenteredView} from 'view/com/util/Views' import {CenteredView} from 'view/com/util/Views'
import {Logo} from 'view/icons/Logo' import {Logo} from 'view/icons/Logo'
@@ -95,7 +97,7 @@ function LandingScreenLoaded({
setScreenState: (state: LoggedOutScreenState) => void setScreenState: (state: LoggedOutScreenState) => void
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}) { }) {
const {record, creator, listItemsSample, feeds, joinedWeekCount} = starterPack const {record, creator, listItemsSample, feeds} = starterPack
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const activeStarterPack = useActiveStarterPack() const activeStarterPack = useActiveStarterPack()
@@ -200,24 +202,22 @@ function LandingScreenLoaded({
<Trans>Join Bluesky</Trans> <Trans>Join Bluesky</Trans>
</ButtonText> </ButtonText>
</Button> </Button>
{joinedWeekCount && joinedWeekCount >= 25 ? ( <View style={[a.flex_row, a.align_center, a.gap_sm]}>
<View style={[a.flex_row, a.align_center, a.gap_sm]}> <FontAwesomeIcon
<FontAwesomeIcon icon="arrow-trend-up"
icon="arrow-trend-up" size={12}
size={12} color={t.atoms.text_contrast_medium.color}
color={t.atoms.text_contrast_medium.color} />
/> <Text
<Text style={[
style={[ a.font_semibold,
a.font_semibold, a.text_sm,
a.text_sm, t.atoms.text_contrast_medium,
t.atoms.text_contrast_medium, ]}
]} numberOfLines={1}>
numberOfLines={1}> <Trans>{formatCount(JOINED_THIS_WEEK)} joined this week</Trans>
123,659 joined this week </Text>
</Text> </View>
</View>
) : null}
</View> </View>
<View style={[a.gap_3xl]}> <View style={[a.gap_3xl]}>
{Boolean(listItemsSample?.length) && ( {Boolean(listItemsSample?.length) && (
@@ -231,18 +231,26 @@ function LandingScreenLoaded({
</Trans> </Trans>
)} )}
</Text> </Text>
<View> <View
style={
isTabletOrDesktop && [
a.border,
a.rounded_md,
t.atoms.border_contrast_low,
]
}>
{starterPack.listItemsSample {starterPack.listItemsSample
?.filter(p => !p.subject.associated?.labeler) ?.filter(p => !p.subject.associated?.labeler)
.slice(0, 8) .slice(0, 8)
.map(item => ( .map((item, i) => (
<View <View
key={item.subject.did} key={item.subject.did}
style={[ style={[
a.py_lg, a.py_lg,
a.px_md, a.px_md,
a.border_t, (!isTabletOrDesktop || i !== 0) && a.border_t,
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
{pointerEvents: 'none'},
]}> ]}>
<ProfileCard <ProfileCard
profile={item.subject} profile={item.subject}
@@ -259,13 +267,21 @@ function LandingScreenLoaded({
<Trans>You'll stay updated with these feeds</Trans> <Trans>You'll stay updated with these feeds</Trans>
</Text> </Text>
<View style={[{pointerEvents: 'none'}]}> <View
{feeds?.map(feed => ( style={[
{pointerEvents: 'none'},
isTabletOrDesktop && [
a.border,
a.rounded_md,
t.atoms.border_contrast_low,
],
]}>
{feeds?.map((feed, i) => (
<View <View
style={[ style={[
a.py_lg, a.py_lg,
a.px_md, a.px_md,
a.border_t, (!isTabletOrDesktop || i !== 0) && a.border_t,
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
]} ]}
key={feed.uri}> key={feed.uri}>
+12 -5
View File
@@ -7,6 +7,7 @@ import {
AppBskyGraphStarterpack, AppBskyGraphStarterpack,
AtUri, AtUri,
ModerationOpts, ModerationOpts,
RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
@@ -52,6 +53,7 @@ import {Loader} from '#/components/Loader'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import {RichText} from '#/components/RichText'
import {FeedsList} from '#/components/StarterPack/Main/FeedsList' import {FeedsList} from '#/components/StarterPack/Main/FeedsList'
import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList' import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList'
import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog'
@@ -280,6 +282,13 @@ function Header({
return null return null
} }
const richText = record.description
? new RichTextAPI({
text: record.description,
facets: record.descriptionFacets,
})
: undefined
return ( return (
<> <>
<ProfileSubpageHeader <ProfileSubpageHeader
@@ -324,12 +333,10 @@ function Header({
/> />
</View> </View>
</ProfileSubpageHeader> </ProfileSubpageHeader>
{record.description || joinedAllTimeCount >= 25 ? ( {richText || joinedAllTimeCount >= 25 ? (
<View style={[a.px_lg, a.pt_md, a.pb_sm, a.gap_md]}> <View style={[a.px_lg, a.pt_md, a.pb_sm, a.gap_md]}>
{record.description ? ( {richText ? (
<Text style={[a.text_md, a.leading_snug]}> <RichText value={richText} style={[a.text_md, a.leading_snug]} />
{record.description}
</Text>
) : null} ) : null}
{joinedAllTimeCount >= 25 ? ( {joinedAllTimeCount >= 25 ? (
<View style={[a.flex_row, a.align_center, a.gap_sm]}> <View style={[a.flex_row, a.align_center, a.gap_sm]}>
+13 -5
View File
@@ -32,7 +32,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) {
const throttledQuery = useThrottledValue(query, 500) const throttledQuery = useThrottledValue(query, 500)
const {screenReaderEnabled} = useA11y() const {screenReaderEnabled} = useA11y()
const {data: savedFeedsAndLists, isLoading: isLoadingSavedFeeds} = const {data: savedFeedsAndLists, isFetchedAfterMount: isFetchedSavedFeeds} =
useSavedFeeds() useSavedFeeds()
const savedFeeds = savedFeedsAndLists?.feeds const savedFeeds = savedFeedsAndLists?.feeds
.filter(f => f.type === 'feed' && f.view.uri !== DISCOVER_FEED_URI) .filter(f => f.type === 'feed' && f.view.uri !== DISCOVER_FEED_URI)
@@ -46,15 +46,23 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) {
limit: 30, limit: 30,
}) })
const popularFeeds = popularFeedsPages?.pages.flatMap(p => p.feeds) ?? [] const popularFeeds = popularFeedsPages?.pages.flatMap(p => p.feeds) ?? []
const suggestedFeeds = savedFeeds.concat(
popularFeeds.filter(f => !savedFeeds.some(sf => sf.uri === f.uri)), // If we have saved feeds already loaded, display them immediately
) // Then, when popular feeds have loaded we can concat them to the saved feeds
const suggestedFeeds =
savedFeeds || isFetchedSavedFeeds
? popularFeeds
? savedFeeds.concat(
popularFeeds.filter(f => !savedFeeds.some(sf => sf.uri === f.uri)),
)
: savedFeeds
: undefined
const {data: searchedFeeds, isFetching: isFetchingSearchedFeeds} = const {data: searchedFeeds, isFetching: isFetchingSearchedFeeds} =
useSearchPopularFeedsQuery({q: throttledQuery}) useSearchPopularFeedsQuery({q: throttledQuery})
const isLoading = const isLoading =
isLoadingSavedFeeds || isLoadingPopularFeeds || isFetchingSearchedFeeds !isFetchedSavedFeeds || isLoadingPopularFeeds || isFetchingSearchedFeeds
const renderItem = ({ const renderItem = ({
item, item,
-2
View File
@@ -245,7 +245,6 @@ function WizardInner({
editStarterPack({ editStarterPack({
name: state.name?.trim() || getDefaultName(), name: state.name?.trim() || getDefaultName(),
description: state.description?.trim(), description: state.description?.trim(),
descriptionFacets: [],
profiles: state.profiles, profiles: state.profiles,
feeds: state.feeds, feeds: state.feeds,
currentStarterPack: currentStarterPack, currentStarterPack: currentStarterPack,
@@ -255,7 +254,6 @@ function WizardInner({
createStarterPack({ createStarterPack({
name: state.name?.trim() || getDefaultName(), name: state.name?.trim() || getDefaultName(),
description: state.description?.trim(), description: state.description?.trim(),
descriptionFacets: [],
profiles: state.profiles, profiles: state.profiles,
feeds: state.feeds, feeds: state.feeds,
}) })
-9
View File
@@ -5,7 +5,6 @@ import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {GalleryModel} from '#/state/models/media/gallery' import {GalleryModel} from '#/state/models/media/gallery'
import {ImageModel} from '#/state/models/media/image' import {ImageModel} from '#/state/models/media/image'
import {ThreadgateSetting} from '../queries/threadgate'
export interface EditProfileModal { export interface EditProfileModal {
name: 'edit-profile' name: 'edit-profile'
@@ -67,13 +66,6 @@ export interface SelfLabelModal {
onChange: (labels: string[]) => void onChange: (labels: string[]) => void
} }
export interface ThreadgateModal {
name: 'threadgate'
settings: ThreadgateSetting[]
onChange?: (settings: ThreadgateSetting[]) => void
onConfirm?: (settings: ThreadgateSetting[]) => void
}
export interface ChangeHandleModal { export interface ChangeHandleModal {
name: 'change-handle' name: 'change-handle'
onChanged: () => void onChanged: () => void
@@ -149,7 +141,6 @@ export type Modal =
| CropImageModal | CropImageModal
| EditImageModal | EditImageModal
| SelfLabelModal | SelfLabelModal
| ThreadgateModal
// Bluesky access // Bluesky access
| WaitlistModal | WaitlistModal
+36 -14
View File
@@ -4,8 +4,10 @@ import {
AppBskyGraphDefs, AppBskyGraphDefs,
AppBskyGraphGetStarterPack, AppBskyGraphGetStarterPack,
AppBskyGraphStarterpack, AppBskyGraphStarterpack,
AppBskyRichtextFacet,
AtUri, AtUri,
BskyAgent, BskyAgent,
RichText,
} from '@atproto/api' } from '@atproto/api'
import {StarterPackView} from '@atproto/api/dist/client/types/app/bsky/graph/defs' import {StarterPackView} from '@atproto/api/dist/client/types/app/bsky/graph/defs'
import { import {
@@ -80,7 +82,6 @@ export async function invalidateStarterPack({
interface UseCreateStarterPackMutationParams { interface UseCreateStarterPackMutationParams {
name: string name: string
description?: string description?: string
descriptionFacets: []
profiles: AppBskyActorDefs.ProfileViewBasic[] profiles: AppBskyActorDefs.ProfileViewBasic[]
feeds?: AppBskyFeedDefs.GeneratorView[] feeds?: AppBskyFeedDefs.GeneratorView[]
} }
@@ -100,16 +101,33 @@ export function useCreateStarterPackMutation({
Error, Error,
UseCreateStarterPackMutationParams UseCreateStarterPackMutationParams
>({ >({
mutationFn: async params => { mutationFn: async ({name, description, feeds, profiles}) => {
let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined
if (description) {
const rt = new RichText({text: description})
await rt.detectFacets(agent)
descriptionFacets = rt.facets
}
let listRes let listRes
listRes = await createStarterPackList({...params, agent}) listRes = await createStarterPackList({
name,
description,
profiles,
descriptionFacets,
agent,
})
return await agent.app.bsky.graph.starterpack.create( return await agent.app.bsky.graph.starterpack.create(
{ {
repo: agent.session?.did, repo: agent.session?.did,
}, },
{ {
...params, name,
description,
descriptionFacets,
list: listRes?.uri, list: listRes?.uri,
feeds,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
}, },
) )
@@ -148,16 +166,20 @@ export function useEditStarterPackMutation({
currentListItems: AppBskyGraphDefs.ListItemView[] currentListItems: AppBskyGraphDefs.ListItemView[]
} }
>({ >({
mutationFn: async params => { mutationFn: async ({
const { name,
name, description,
description, feeds,
descriptionFacets, profiles,
feeds, currentStarterPack,
profiles, currentListItems,
currentStarterPack, }) => {
currentListItems, let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined
} = params if (description) {
const rt = new RichText({text: description})
await rt.detectFacets(agent)
descriptionFacets = rt.facets
}
if (!AppBskyGraphStarterpack.isRecord(currentStarterPack.record)) { if (!AppBskyGraphStarterpack.isRecord(currentStarterPack.record)) {
throw new Error('Invalid starter pack') throw new Error('Invalid starter pack')
@@ -5,11 +5,12 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useModalControls} from '#/state/modals'
import {ThreadgateSetting} from '#/state/queries/threadgate' import {ThreadgateSetting} from '#/state/queries/threadgate'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {ThreadgateEditorDialog} from '#/components/dialogs/ThreadgateEditor'
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe' import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
@@ -26,18 +27,15 @@ export function ThreadgateBtn({
const {track} = useAnalytics() const {track} = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const {openModal} = useModalControls() const control = Dialog.useDialogControl()
const onPress = () => { const onPress = () => {
track('Composer:ThreadgateOpened') track('Composer:ThreadgateOpened')
if (isNative && Keyboard.isVisible()) { if (isNative && Keyboard.isVisible()) {
Keyboard.dismiss() Keyboard.dismiss()
} }
openModal({
name: 'threadgate', control.open()
settings: threadgate,
onChange,
})
} }
const isEverybody = threadgate.length === 0 const isEverybody = threadgate.length === 0
@@ -49,19 +47,29 @@ export function ThreadgateBtn({
: _(msg`Some people can reply`) : _(msg`Some people can reply`)
return ( return (
<Animated.View style={[a.flex_row, a.p_sm, t.atoms.bg, style]}> <>
<Button <Animated.View style={[a.flex_row, a.p_sm, t.atoms.bg, style]}>
variant="solid" <Button
color="secondary" variant="solid"
size="xsmall" color="secondary"
testID="openReplyGateButton" size="xsmall"
onPress={onPress} testID="openReplyGateButton"
label={label}> onPress={onPress}
<ButtonIcon label={label}
icon={isEverybody ? Earth : isNobody ? CircleBanSign : Group} accessibilityHint={_(
/> msg`Opens a dialog to choose who can reply to this thread`,
<ButtonText>{label}</ButtonText> )}>
</Button> <ButtonIcon
</Animated.View> icon={isEverybody ? Earth : isNobody ? CircleBanSign : Group}
/>
<ButtonText>{label}</ButtonText>
</Button>
</Animated.View>
<ThreadgateEditorDialog
control={control}
threadgate={threadgate}
onChange={onChange}
/>
</>
) )
} }
-4
View File
@@ -23,7 +23,6 @@ import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettin
import * as LinkWarningModal from './LinkWarning' import * as LinkWarningModal from './LinkWarning'
import * as ListAddUserModal from './ListAddRemoveUsers' import * as ListAddUserModal from './ListAddRemoveUsers'
import * as SelfLabelModal from './SelfLabel' import * as SelfLabelModal from './SelfLabel'
import * as ThreadgateModal from './Threadgate'
import * as UserAddRemoveListsModal from './UserAddRemoveLists' import * as UserAddRemoveListsModal from './UserAddRemoveLists'
import * as VerifyEmailModal from './VerifyEmail' import * as VerifyEmailModal from './VerifyEmail'
@@ -76,9 +75,6 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'self-label') { } else if (activeModal?.name === 'self-label') {
snapPoints = SelfLabelModal.snapPoints snapPoints = SelfLabelModal.snapPoints
element = <SelfLabelModal.Component {...activeModal} /> element = <SelfLabelModal.Component {...activeModal} />
} else if (activeModal?.name === 'threadgate') {
snapPoints = ThreadgateModal.snapPoints
element = <ThreadgateModal.Component {...activeModal} />
} else if (activeModal?.name === 'alt-text-image') { } else if (activeModal?.name === 'alt-text-image') {
snapPoints = AltImageModal.snapPoints snapPoints = AltImageModal.snapPoints
element = <AltImageModal.Component {...activeModal} /> element = <AltImageModal.Component {...activeModal} />
-3
View File
@@ -23,7 +23,6 @@ import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettin
import * as LinkWarningModal from './LinkWarning' import * as LinkWarningModal from './LinkWarning'
import * as ListAddUserModal from './ListAddRemoveUsers' import * as ListAddUserModal from './ListAddRemoveUsers'
import * as SelfLabelModal from './SelfLabel' import * as SelfLabelModal from './SelfLabel'
import * as ThreadgateModal from './Threadgate'
import * as UserAddRemoveLists from './UserAddRemoveLists' import * as UserAddRemoveLists from './UserAddRemoveLists'
import * as VerifyEmailModal from './VerifyEmail' import * as VerifyEmailModal from './VerifyEmail'
@@ -84,8 +83,6 @@ function Modal({modal}: {modal: ModalIface}) {
element = <DeleteAccountModal.Component /> element = <DeleteAccountModal.Component />
} else if (modal.name === 'self-label') { } else if (modal.name === 'self-label') {
element = <SelfLabelModal.Component {...modal} /> element = <SelfLabelModal.Component {...modal} />
} else if (modal.name === 'threadgate') {
element = <ThreadgateModal.Component {...modal} />
} else if (modal.name === 'change-handle') { } else if (modal.name === 'change-handle') {
element = <ChangeHandleModal.Component {...modal} /> element = <ChangeHandleModal.Component {...modal} />
} else if (modal.name === 'invite-codes') { } else if (modal.name === 'invite-codes') {
-208
View File
@@ -1,208 +0,0 @@
import React, {useState} from 'react'
import {
Pressable,
StyleProp,
StyleSheet,
TouchableOpacity,
View,
ViewStyle,
} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import isEqual from 'lodash.isequal'
import {useModalControls} from '#/state/modals'
import {useMyListsQuery} from '#/state/queries/my-lists'
import {ThreadgateSetting} from '#/state/queries/threadgate'
import {usePalette} from 'lib/hooks/usePalette'
import {colors, s} from 'lib/styles'
import {isWeb} from 'platform/detection'
import {ScrollView} from 'view/com/modals/util'
import {Text} from '../util/text/Text'
export const snapPoints = ['60%']
export function Component({
settings,
onChange,
onConfirm,
}: {
settings: ThreadgateSetting[]
onChange?: (settings: ThreadgateSetting[]) => void
onConfirm?: (settings: ThreadgateSetting[]) => void
}) {
const pal = usePalette('default')
const {closeModal} = useModalControls()
const [selected, setSelected] = useState(settings)
const {_} = useLingui()
const {data: lists} = useMyListsQuery('curate')
const onPressEverybody = () => {
setSelected([])
onChange?.([])
}
const onPressNobody = () => {
setSelected([{type: 'nobody'}])
onChange?.([{type: 'nobody'}])
}
const onPressAudience = (setting: ThreadgateSetting) => {
// remove nobody
let newSelected = selected.filter(v => v.type !== 'nobody')
// toggle
const i = newSelected.findIndex(v => isEqual(v, setting))
if (i === -1) {
newSelected.push(setting)
} else {
newSelected.splice(i, 1)
}
setSelected(newSelected)
onChange?.(newSelected)
}
return (
<View testID="threadgateModal" style={[pal.view, styles.container]}>
<View style={styles.titleSection}>
<Text type="title-lg" style={[pal.text, styles.title]}>
<Trans>Who can reply</Trans>
</Text>
</View>
<ScrollView>
<Text style={[pal.text, styles.description]}>
<Trans>Choose "Everybody" or "Nobody"</Trans>
</Text>
<View style={{flexDirection: 'row', gap: 6, paddingHorizontal: 6}}>
<Selectable
label={_(msg`Everybody`)}
isSelected={selected.length === 0}
onPress={onPressEverybody}
style={{flex: 1}}
/>
<Selectable
label={_(msg`Nobody`)}
isSelected={!!selected.find(v => v.type === 'nobody')}
onPress={onPressNobody}
style={{flex: 1}}
/>
</View>
<Text style={[pal.text, styles.description]}>
<Trans>Or combine these options:</Trans>
</Text>
<View style={{flexDirection: 'column', gap: 4, paddingHorizontal: 6}}>
<Selectable
label={_(msg`Mentioned users`)}
isSelected={!!selected.find(v => v.type === 'mention')}
onPress={() => onPressAudience({type: 'mention'})}
/>
<Selectable
label={_(msg`Followed users`)}
isSelected={!!selected.find(v => v.type === 'following')}
onPress={() => onPressAudience({type: 'following'})}
/>
{lists?.length
? lists.map(list => (
<Selectable
key={list.uri}
label={_(msg`Users in "${list.name}"`)}
isSelected={
!!selected.find(
v => v.type === 'list' && v.list === list.uri,
)
}
onPress={() =>
onPressAudience({type: 'list', list: list.uri})
}
/>
))
: null}
</View>
</ScrollView>
<View style={[styles.btnContainer, pal.borderDark]}>
<TouchableOpacity
testID="confirmBtn"
onPress={() => {
closeModal()
onConfirm?.(selected)
}}
style={styles.btn}
accessibilityRole="button"
accessibilityLabel={_(msg({message: `Done`, context: 'action'}))}
accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}>
<Trans context="action">Done</Trans>
</Text>
</TouchableOpacity>
</View>
</View>
)
}
function Selectable({
label,
isSelected,
onPress,
style,
}: {
label: string
isSelected: boolean
onPress: () => void
style?: StyleProp<ViewStyle>
}) {
const pal = usePalette(isSelected ? 'inverted' : 'default')
return (
<Pressable
onPress={onPress}
accessibilityLabel={label}
accessibilityHint=""
style={[styles.selectable, pal.border, pal.view, style]}>
<Text type="lg" style={[pal.text]}>
{label}
</Text>
{isSelected ? (
<FontAwesomeIcon icon="check" color={pal.colors.text} size={18} />
) : null}
</Pressable>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isWeb ? 0 : 40,
},
titleSection: {
paddingTop: isWeb ? 0 : 4,
},
title: {
textAlign: 'center',
fontWeight: '600',
},
description: {
textAlign: 'center',
paddingVertical: 16,
},
selectable: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 18,
paddingVertical: 16,
borderWidth: 1,
borderRadius: 6,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 32,
padding: 14,
backgroundColor: colors.blue3,
},
btnContainer: {
paddingTop: 20,
paddingHorizontal: 20,
},
})