Compare commits
4 Commits
thread-bug
..
sonner
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c988e6471 | |||
| 4199aab5cc | |||
| 185840d389 | |||
| dbcef53e70 |
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '16.4'
|
||||
xcode-version: '16.2'
|
||||
|
||||
- name: ☕️ Setup Cocoapods
|
||||
uses: maxim-lobanov/setup-cocoapods@v1
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M15.793 10.293a1 1 0 0 1 1.338-.068l.076.068 3.293 3.293a2 2 0 0 1 .138 2.677l-.138.151-3.293 3.293a1 1 0 1 1-1.414-1.414L18.086 16H8a5 5 0 0 1-5-5V5a1 1 0 0 1 2 0v6a3 3 0 0 0 3 3h10.086l-2.293-2.293-.068-.076a1 1 0 0 1 .068-1.338Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 334 B |
@@ -1,63 +0,0 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {useSession} from '#/state/session'
|
||||
import {UserInfoText} from '#/view/com/util/UserInfoText'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ArrowCornerDownRight_Stroke2_Corner2_Rounded as ArrowCornerDownRightIcon} from '#/components/icons/ArrowCornerDownRight'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
import {Text} from '#/components/Typography'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
export function PostRepliedTo({
|
||||
parentAuthor,
|
||||
isParentBlocked,
|
||||
isParentNotFound,
|
||||
}: {
|
||||
parentAuthor: string | bsky.profile.AnyProfileView | undefined
|
||||
isParentBlocked?: boolean
|
||||
isParentNotFound?: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const textStyle = [a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]
|
||||
|
||||
let label
|
||||
if (isParentBlocked) {
|
||||
label = <Trans context="description">Replied to a blocked post</Trans>
|
||||
} else if (isParentNotFound) {
|
||||
label = <Trans context="description">Replied to a post</Trans>
|
||||
} else if (parentAuthor) {
|
||||
const did =
|
||||
typeof parentAuthor === 'string' ? parentAuthor : parentAuthor.did
|
||||
const isMe = currentAccount?.did === did
|
||||
if (isMe) {
|
||||
label = <Trans context="description">Replied to you</Trans>
|
||||
} else {
|
||||
label = (
|
||||
<Trans context="description">
|
||||
Replied to{' '}
|
||||
<ProfileHoverCard did={did}>
|
||||
<UserInfoText did={did} attr="displayName" style={textStyle} />
|
||||
</ProfileHoverCard>
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!label) {
|
||||
// Should not happen.
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[a.flex_row, a.align_center, a.pb_xs, a.gap_xs]}>
|
||||
<ArrowCornerDownRightIcon
|
||||
size="xs"
|
||||
style={[t.atoms.text_contrast_medium, {top: -1}]}
|
||||
/>
|
||||
<Text style={textStyle}>{label}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -539,7 +539,7 @@ let Tab = ({
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
a.font_medium,
|
||||
/* TODO: medium weight */
|
||||
active || hovered || pressed || focused
|
||||
? t.atoms.text
|
||||
: t.atoms.text_contrast_medium,
|
||||
|
||||
@@ -92,7 +92,7 @@ export function ToastText({children}: {children: React.ReactNode}) {
|
||||
selectable={false}
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_medium,
|
||||
a.font_bold,
|
||||
a.leading_snug,
|
||||
a.pointer_events_none,
|
||||
{
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const ArrowCornerDownRight_Stroke2_Corner2_Rounded = createSinglePathSVG(
|
||||
{
|
||||
path: 'M15.793 10.293a1 1 0 0 1 1.338-.068l.076.068 3.293 3.293a2 2 0 0 1 .138 2.677l-.138.151-3.293 3.293a1 1 0 1 1-1.414-1.414L18.086 16H8a5 5 0 0 1-5-5V5a1 1 0 0 1 2 0v6a3 3 0 0 0 3 3h10.086l-2.293-2.293-.068-.076a1 1 0 0 1 .068-1.338Z',
|
||||
},
|
||||
)
|
||||
@@ -2685,6 +2685,11 @@ msgstr ""
|
||||
msgid "Dismiss interests"
|
||||
msgstr ""
|
||||
|
||||
#. Accessibility label for dismissing a toast notification
|
||||
#: src/components/Toast/index.web.tsx:82
|
||||
msgid "Dismiss message"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/interstitials/TrendingVideos.tsx:89
|
||||
msgid "Dismiss this section"
|
||||
msgstr ""
|
||||
@@ -3805,7 +3810,7 @@ msgstr ""
|
||||
msgid "From @{sanitizedAuthor}"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/posts/PostFeedItem.tsx:326
|
||||
#: src/view/com/posts/PostFeedItem.tsx:330
|
||||
msgctxt "from-feed"
|
||||
msgid "From <0/>"
|
||||
msgstr ""
|
||||
@@ -6106,7 +6111,7 @@ msgstr ""
|
||||
msgid "Pin to your profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/posts/PostFeedItem.tsx:407
|
||||
#: src/view/com/posts/PostFeedItem.tsx:411
|
||||
msgid "Pinned"
|
||||
msgstr ""
|
||||
|
||||
@@ -6873,26 +6878,6 @@ msgstr ""
|
||||
msgid "Replace with Discover"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Post/PostRepliedTo.tsx:39
|
||||
msgctxt "description"
|
||||
msgid "Replied to <0><1/></0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Post/PostRepliedTo.tsx:28
|
||||
msgctxt "description"
|
||||
msgid "Replied to a blocked post"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Post/PostRepliedTo.tsx:30
|
||||
msgctxt "description"
|
||||
msgid "Replied to a post"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Post/PostRepliedTo.tsx:36
|
||||
msgctxt "description"
|
||||
msgid "Replied to you"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/activity-notifications/SubscribeProfileDialog.tsx:267
|
||||
#: src/components/activity-notifications/SubscribeProfileDialog.tsx:279
|
||||
#: src/lib/hooks/useNotificationHandler.ts:140
|
||||
@@ -6948,6 +6933,28 @@ msgstr ""
|
||||
msgid "Reply sorting"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/post/Post.tsx:204
|
||||
#: src/view/com/posts/PostFeedItem.tsx:602
|
||||
msgctxt "description"
|
||||
msgid "Reply to <0><1/></0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/posts/PostFeedItem.tsx:593
|
||||
msgctxt "description"
|
||||
msgid "Reply to a blocked post"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/posts/PostFeedItem.tsx:595
|
||||
msgctxt "description"
|
||||
msgid "Reply to a post"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/post/Post.tsx:202
|
||||
#: src/view/com/posts/PostFeedItem.tsx:599
|
||||
msgctxt "description"
|
||||
msgid "Reply to you"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:335
|
||||
msgctxt "toast"
|
||||
msgid "Reply visibility updated"
|
||||
@@ -7073,16 +7080,16 @@ msgstr ""
|
||||
msgid "Reposted By"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/posts/PostFeedItem.tsx:347
|
||||
#: src/view/com/posts/PostFeedItem.tsx:351
|
||||
msgid "Reposted by {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/posts/PostFeedItem.tsx:366
|
||||
#: src/view/com/posts/PostFeedItem.tsx:370
|
||||
msgid "Reposted by <0><1/></0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/posts/PostFeedItem.tsx:345
|
||||
#: src/view/com/posts/PostFeedItem.tsx:364
|
||||
#: src/view/com/posts/PostFeedItem.tsx:349
|
||||
#: src/view/com/posts/PostFeedItem.tsx:368
|
||||
msgid "Reposted by you"
|
||||
msgstr ""
|
||||
|
||||
@@ -8098,7 +8105,7 @@ msgstr ""
|
||||
msgid "Something wrong? Let us know."
|
||||
msgstr ""
|
||||
|
||||
#: src/App.native.tsx:126
|
||||
#: src/App.native.tsx:125
|
||||
#: src/App.web.tsx:102
|
||||
msgid "Sorry! Your session expired. Please sign in again."
|
||||
msgstr ""
|
||||
@@ -9625,7 +9632,7 @@ msgstr ""
|
||||
msgid "Videos must be less than 3 minutes long"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Profile/Header/Shell.tsx:229
|
||||
#: src/screens/Profile/Header/Shell.tsx:211
|
||||
msgid "View {0}'s avatar"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -254,7 +254,6 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
langs: record.langs,
|
||||
},
|
||||
onPostSuccess: onPostSuccess,
|
||||
})
|
||||
|
||||
@@ -234,7 +234,6 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
langs: post.record.langs,
|
||||
},
|
||||
onPostSuccess: onPostSuccess,
|
||||
})
|
||||
|
||||
@@ -298,7 +298,6 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
langs: post.record.langs,
|
||||
},
|
||||
onPostSuccess: onPostSuccess,
|
||||
})
|
||||
|
||||
@@ -91,7 +91,6 @@ export function PostThread({uri}: {uri: string}) {
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation: anchor.moderation,
|
||||
langs: post.record.langs,
|
||||
},
|
||||
onPostSuccess: optimisticOnPostReply,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {memo, useCallback, useEffect, useMemo} from 'react'
|
||||
import {TouchableWithoutFeedback, View} from 'react-native'
|
||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
||||
import Animated, {
|
||||
measure,
|
||||
type MeasuredDimensions,
|
||||
@@ -26,8 +26,6 @@ import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {UserBanner} from '#/view/com/util/UserBanner'
|
||||
import {atoms as a, platform, useTheme} from '#/alf'
|
||||
import {transparentifyColor} from '#/alf/util/colorGeneration'
|
||||
import {Button} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow'
|
||||
import {EditLiveDialog} from '#/components/live/EditLiveDialog'
|
||||
@@ -150,46 +148,30 @@ let ProfileHeaderShell = ({
|
||||
<StatusBarShadow />
|
||||
<GrowableBanner
|
||||
backButton={
|
||||
!hideBackButton && (
|
||||
<Button
|
||||
testID="profileHeaderBackBtn"
|
||||
onPress={onPressBack}
|
||||
hitSlop={BACK_HITSLOP}
|
||||
label={_(msg`Back`)}
|
||||
style={[
|
||||
a.absolute,
|
||||
a.pointer,
|
||||
{
|
||||
top: platform({
|
||||
web: 10,
|
||||
default: topInset,
|
||||
}),
|
||||
left: platform({
|
||||
web: 18,
|
||||
default: 12,
|
||||
}),
|
||||
},
|
||||
]}>
|
||||
{({hovered}) => (
|
||||
<>
|
||||
{!hideBackButton && (
|
||||
<TouchableWithoutFeedback
|
||||
testID="profileHeaderBackBtn"
|
||||
onPress={onPressBack}
|
||||
hitSlop={BACK_HITSLOP}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Back`)}
|
||||
accessibilityHint="">
|
||||
<View
|
||||
style={[
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.rounded_sm,
|
||||
styles.backBtnWrapper,
|
||||
{
|
||||
width: 31,
|
||||
height: 31,
|
||||
backgroundColor: transparentifyColor('#000', 0.5),
|
||||
},
|
||||
hovered && {
|
||||
backgroundColor: transparentifyColor('#000', 0.75),
|
||||
top: platform({
|
||||
web: 10,
|
||||
default: topInset,
|
||||
}),
|
||||
},
|
||||
]}>
|
||||
<ArrowLeftIcon size="lg" fill="white" />
|
||||
</View>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
</TouchableWithoutFeedback>
|
||||
)}
|
||||
</>
|
||||
}>
|
||||
{isPlaceholderProfile ? (
|
||||
<LoadingPlaceholder
|
||||
@@ -221,7 +203,7 @@ let ProfileHeaderShell = ({
|
||||
</View>
|
||||
)}
|
||||
|
||||
<GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}>
|
||||
<GrowableAvatar style={styles.aviPosition}>
|
||||
<TouchableWithoutFeedback
|
||||
testID="profileHeaderAviButton"
|
||||
onPress={onPressAvi}
|
||||
@@ -233,14 +215,13 @@ let ProfileHeaderShell = ({
|
||||
t.atoms.bg,
|
||||
a.rounded_full,
|
||||
{
|
||||
width: 94,
|
||||
height: 94,
|
||||
borderWidth: live.isActive ? 3 : 2,
|
||||
borderColor: live.isActive
|
||||
? t.palette.negative_500
|
||||
: t.atoms.bg.backgroundColor,
|
||||
},
|
||||
profile.associated?.labeler && a.rounded_md,
|
||||
styles.avi,
|
||||
profile.associated?.labeler && styles.aviLabeler,
|
||||
]}>
|
||||
<Animated.View ref={aviRef} collapsable={false}>
|
||||
<UserAvatar
|
||||
@@ -274,6 +255,39 @@ let ProfileHeaderShell = ({
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
ProfileHeaderShell = memo(ProfileHeaderShell)
|
||||
export {ProfileHeaderShell}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
backBtnWrapper: {
|
||||
position: 'absolute',
|
||||
left: 12,
|
||||
width: 30,
|
||||
height: 30,
|
||||
overflow: 'hidden',
|
||||
borderRadius: 15,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
backBtn: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
aviPosition: {
|
||||
position: 'absolute',
|
||||
top: 104,
|
||||
left: 10,
|
||||
},
|
||||
avi: {
|
||||
width: 94,
|
||||
height: 94,
|
||||
},
|
||||
aviLabeler: {
|
||||
borderRadius: 10,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -147,7 +147,7 @@ let Tab = ({
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
a.font_medium,
|
||||
/* TODO: medium weight */
|
||||
active || hovered || pressed || focused
|
||||
? t.atoms.text
|
||||
: t.atoms.text_contrast_medium,
|
||||
|
||||
@@ -163,7 +163,10 @@ function TrendingIndicator({type}: {type: TrendingIndicatorType | 'skeleton'}) {
|
||||
a.align_center,
|
||||
a.gap_xs,
|
||||
a.rounded_full,
|
||||
{height: 28, paddingHorizontal: 10},
|
||||
a.px_sm,
|
||||
{
|
||||
height: 28,
|
||||
},
|
||||
]
|
||||
|
||||
let Icon: React.ComponentType<SVGIconProps> | null = null
|
||||
@@ -215,7 +218,7 @@ function TrendingIndicator({type}: {type: TrendingIndicatorType | 'skeleton'}) {
|
||||
return (
|
||||
<View style={[pillStyles, {backgroundColor}]}>
|
||||
{Icon && <Icon size="sm" style={{color}} />}
|
||||
<Text style={[a.text_sm, a.font_medium, {color}]}>{text}</Text>
|
||||
<Text style={[a.text_sm, {color}]}>{text}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -753,7 +753,6 @@ function Overlay({
|
||||
text: record?.text || '',
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
langs: record?.langs,
|
||||
},
|
||||
})
|
||||
}, [openComposer, post, record])
|
||||
|
||||
@@ -307,16 +307,9 @@ export function sortAndAnnotateThreadItems(
|
||||
metadata.isPartOfLastBranchFromDepth = metadata.depth
|
||||
|
||||
/**
|
||||
* If the parent is part of the last branch of the sub-tree, so
|
||||
* is the child. However, if the child is also a last sibling,
|
||||
* then we need to start tracking `isPartOfLastBranchFromDepth`
|
||||
* from this point onwards, always updating it to the depth of
|
||||
* the last sibling as we go down.
|
||||
* If the parent is part of the last branch of the sub-tree, so is the child.
|
||||
*/
|
||||
if (
|
||||
!metadata.isLastSibling &&
|
||||
metadata.parentMetadata.isPartOfLastBranchFromDepth
|
||||
) {
|
||||
if (metadata.parentMetadata.isPartOfLastBranchFromDepth) {
|
||||
metadata.isPartOfLastBranchFromDepth =
|
||||
metadata.parentMetadata.isPartOfLastBranchFromDepth
|
||||
}
|
||||
|
||||
@@ -151,8 +151,8 @@ export type TraversalMetadata = {
|
||||
*/
|
||||
isLastChild: boolean
|
||||
/**
|
||||
* Indicates if the post is the left-most AND lower-most branch of the reply
|
||||
* tree. Value corresponds to the depth at which this branch started.
|
||||
* Indicates if the post is the left/lower-most branch of the reply tree.
|
||||
* Value corresponds to the depth at which this branch started.
|
||||
*/
|
||||
isPartOfLastBranchFromDepth?: number
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,6 @@ export interface ComposerOptsPostRef {
|
||||
uri: string
|
||||
cid: string
|
||||
text: string
|
||||
langs?: string[]
|
||||
author: AppBskyActorDefs.ProfileViewBasic
|
||||
embed?: AppBskyFeedDefs.PostView['embed']
|
||||
moderation?: ModerationDecision
|
||||
|
||||
@@ -621,11 +621,7 @@ export const ComposePost = ({
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<SuggestedLanguage
|
||||
text={activePost.richtext.text}
|
||||
// NOTE(@elijaharita): currently just choosing the first language if any exists
|
||||
replyToLanguage={replyTo?.langs?.[0]}
|
||||
/>
|
||||
<SuggestedLanguage text={activePost.richtext.text} />
|
||||
<ComposerPills
|
||||
isReply={!!replyTo}
|
||||
post={activePost}
|
||||
|
||||
@@ -19,29 +19,16 @@ import {Text} from '#/components/Typography'
|
||||
const onIdle = globalThis.requestIdleCallback || (cb => setTimeout(cb, 1))
|
||||
const cancelIdle = globalThis.cancelIdleCallback || clearTimeout
|
||||
|
||||
export function SuggestedLanguage({
|
||||
text,
|
||||
replyToLanguage,
|
||||
}: {
|
||||
text: string
|
||||
replyToLanguage?: string
|
||||
}) {
|
||||
export function SuggestedLanguage({text}: {text: string}) {
|
||||
const [suggestedLanguage, setSuggestedLanguage] = useState<
|
||||
string | undefined
|
||||
>(text.length === 0 ? replyToLanguage : undefined)
|
||||
>()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const setLangPrefs = useLanguagePrefsApi()
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
useEffect(() => {
|
||||
// For replies, suggest the language of the post being replied to if no text
|
||||
// has been typed yet
|
||||
if (replyToLanguage && text.length === 0) {
|
||||
setSuggestedLanguage(replyToLanguage)
|
||||
return
|
||||
}
|
||||
|
||||
const textTrimmed = text.trim()
|
||||
|
||||
// Don't run the language model on small posts, the results are likely
|
||||
@@ -56,7 +43,7 @@ export function SuggestedLanguage({
|
||||
})
|
||||
|
||||
return () => cancelIdle(idle)
|
||||
}, [text, replyToLanguage])
|
||||
}, [text])
|
||||
|
||||
if (
|
||||
suggestedLanguage &&
|
||||
|
||||
@@ -220,11 +220,7 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
||||
textInputWebEmitter.emit('publish')
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
event.code === 'Backspace' &&
|
||||
!(event.metaKey || event.altKey || event.ctrlKey)
|
||||
) {
|
||||
if (event.code === 'Backspace') {
|
||||
const isNotSelection = view.state.selection.empty
|
||||
if (isNotSelection) {
|
||||
const cursorPosition = view.state.selection.$anchor.pos
|
||||
|
||||
@@ -420,7 +420,6 @@ export function PostThread({uri}: {uri: string}) {
|
||||
author: thread.post.author,
|
||||
embed: thread.post.embed,
|
||||
moderation: threadModerationCache.get(thread),
|
||||
langs: thread.record.langs,
|
||||
},
|
||||
onPost: onPostReply,
|
||||
})
|
||||
|
||||
@@ -299,7 +299,6 @@ let PostThreadItemLoaded = ({
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
langs: record.langs,
|
||||
},
|
||||
onPost: onPostReply,
|
||||
onPostSuccess: onPostSuccess,
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
type ModerationDecision,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {MAX_POST_LINES} from '#/lib/constants'
|
||||
@@ -15,25 +17,28 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {colors} from '#/lib/styles'
|
||||
import {colors, s} from '#/lib/styles'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
usePostShadow,
|
||||
} from '#/state/cache/post-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {unstableCacheProfileView} from '#/state/queries/profile'
|
||||
import {precacheProfile} from '#/state/queries/profile'
|
||||
import {useSession} from '#/state/session'
|
||||
import {Link} from '#/view/com/util/Link'
|
||||
import {PostMeta} from '#/view/com/util/PostMeta'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {UserInfoText} from '#/view/com/util/UserInfoText'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
||||
import {PostRepliedTo} from '#/components/Post/PostRepliedTo'
|
||||
import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton'
|
||||
import {PostControls} from '#/components/PostControls'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {SubtleWebHover} from '#/components/SubtleWebHover'
|
||||
import * as bsky from '#/types/bsky'
|
||||
@@ -131,7 +136,6 @@ function PostInner({
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
langs: record.langs,
|
||||
},
|
||||
})
|
||||
}, [openComposer, post, record, moderation])
|
||||
@@ -141,9 +145,12 @@ function PostInner({
|
||||
}, [setLimitLines])
|
||||
|
||||
const onBeforePress = useCallback(() => {
|
||||
unstableCacheProfileView(queryClient, post.author)
|
||||
precacheProfile(queryClient, post.author)
|
||||
}, [queryClient, post.author])
|
||||
|
||||
const {currentAccount} = useSession()
|
||||
const isMe = replyAuthorDid === currentAccount?.did
|
||||
|
||||
const [hover, setHover] = useState(false)
|
||||
return (
|
||||
<Link
|
||||
@@ -180,7 +187,34 @@ function PostInner({
|
||||
postHref={itemHref}
|
||||
/>
|
||||
{replyAuthorDid !== '' && (
|
||||
<PostRepliedTo parentAuthor={replyAuthorDid} />
|
||||
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
|
||||
<FontAwesomeIcon
|
||||
icon="reply"
|
||||
size={9}
|
||||
style={[pal.textLight, s.mr5]}
|
||||
/>
|
||||
<Text
|
||||
type="sm"
|
||||
style={[pal.textLight, s.mr2]}
|
||||
lineHeight={1.2}
|
||||
numberOfLines={1}>
|
||||
{isMe ? (
|
||||
<Trans context="description">Reply to you</Trans>
|
||||
) : (
|
||||
<Trans context="description">
|
||||
Reply to{' '}
|
||||
<ProfileHoverCard did={replyAuthorDid}>
|
||||
<UserInfoText
|
||||
type="sm"
|
||||
did={replyAuthorDid}
|
||||
attr="displayName"
|
||||
style={[pal.textLight]}
|
||||
/>
|
||||
</ProfileHoverCard>
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<LabelsOnMyPost post={post} />
|
||||
<ContentHider
|
||||
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
type ModerationDecision,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
type FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
@@ -22,6 +26,7 @@ import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {s} from '#/lib/styles'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
@@ -49,7 +54,6 @@ import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||
import {type AppModerationCause} from '#/components/Pills'
|
||||
import {Embed} from '#/components/Post/Embed'
|
||||
import {PostEmbedViewContext} from '#/components/Post/Embed/types'
|
||||
import {PostRepliedTo} from '#/components/Post/PostRepliedTo'
|
||||
import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton'
|
||||
import {PostControls} from '#/components/PostControls'
|
||||
import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug'
|
||||
@@ -193,7 +197,6 @@ let FeedItemInner = ({
|
||||
author: post.author,
|
||||
embed: post.embed,
|
||||
moderation,
|
||||
langs: record.langs,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -445,10 +448,10 @@ let FeedItemInner = ({
|
||||
/>
|
||||
{showReplyTo &&
|
||||
(parentAuthor || isParentBlocked || isParentNotFound) && (
|
||||
<PostRepliedTo
|
||||
parentAuthor={parentAuthor}
|
||||
isParentBlocked={isParentBlocked}
|
||||
isParentNotFound={isParentNotFound}
|
||||
<ReplyToLabel
|
||||
blocked={isParentBlocked}
|
||||
notFound={isParentNotFound}
|
||||
profile={parentAuthor}
|
||||
/>
|
||||
)}
|
||||
<LabelsOnMyPost post={post} />
|
||||
@@ -573,10 +576,80 @@ let PostContent = ({
|
||||
}
|
||||
PostContent = memo(PostContent)
|
||||
|
||||
function ReplyToLabel({
|
||||
profile,
|
||||
blocked,
|
||||
notFound,
|
||||
}: {
|
||||
profile: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
blocked?: boolean
|
||||
notFound?: boolean
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
let label
|
||||
if (blocked) {
|
||||
label = <Trans context="description">Reply to a blocked post</Trans>
|
||||
} else if (notFound) {
|
||||
label = <Trans context="description">Reply to a post</Trans>
|
||||
} else if (profile != null) {
|
||||
const isMe = profile.did === currentAccount?.did
|
||||
if (isMe) {
|
||||
label = <Trans context="description">Reply to you</Trans>
|
||||
} else {
|
||||
label = (
|
||||
<Trans context="description">
|
||||
Reply to{' '}
|
||||
<ProfileHoverCard did={profile.did}>
|
||||
<TextLinkOnWebOnly
|
||||
type="md"
|
||||
style={pal.textLight}
|
||||
lineHeight={1.2}
|
||||
numberOfLines={1}
|
||||
href={makeProfileLink(profile)}
|
||||
text={
|
||||
<Text emoji type="md" style={pal.textLight} lineHeight={1.2}>
|
||||
{profile.displayName
|
||||
? sanitizeDisplayName(profile.displayName)
|
||||
: sanitizeHandle(profile.handle)}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</ProfileHoverCard>
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!label) {
|
||||
// Should not happen.
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
|
||||
<FontAwesomeIcon
|
||||
icon="reply"
|
||||
size={9}
|
||||
style={[{color: pal.colors.textLight} as FontAwesomeIconStyle, s.mr5]}
|
||||
/>
|
||||
<Text
|
||||
type="md"
|
||||
style={[pal.textLight, s.mr2]}
|
||||
lineHeight={1.2}
|
||||
numberOfLines={1}>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
paddingLeft: 10,
|
||||
paddingRight: 15,
|
||||
// @ts-ignore web only -prf
|
||||
cursor: 'pointer',
|
||||
},
|
||||
replyLine: {
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import {type StyleProp, type TextStyle} from 'react-native'
|
||||
import {type AppBskyActorGetProfile} from '@atproto/api'
|
||||
import {StyleProp, StyleSheet, TextStyle} from 'react-native'
|
||||
import {AppBskyActorGetProfile as GetProfile} from '@atproto/api'
|
||||
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {TypographyVariant} from '#/lib/ThemeContext'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {TextLinkOnWebOnly} from './Link'
|
||||
import {LoadingPlaceholder} from './LoadingPlaceholder'
|
||||
import {Text} from './text/Text'
|
||||
|
||||
export function UserInfoText({
|
||||
type = 'md',
|
||||
did,
|
||||
attr,
|
||||
failed,
|
||||
prefix,
|
||||
style,
|
||||
}: {
|
||||
type?: TypographyVariant
|
||||
did: string
|
||||
attr?: keyof AppBskyActorGetProfile.OutputSchema
|
||||
attr?: keyof GetProfile.OutputSchema
|
||||
loading?: string
|
||||
failed?: string
|
||||
prefix?: string
|
||||
@@ -33,37 +35,45 @@ export function UserInfoText({
|
||||
staleTime: STALE.INFINITY,
|
||||
})
|
||||
|
||||
let inner
|
||||
if (isError) {
|
||||
return (
|
||||
<Text style={style} numberOfLines={1}>
|
||||
inner = (
|
||||
<Text type={type} style={style} numberOfLines={1}>
|
||||
{failed}
|
||||
</Text>
|
||||
)
|
||||
} else if (profile) {
|
||||
const text = `${prefix || ''}${sanitizeDisplayName(
|
||||
typeof profile[attr] === 'string' && profile[attr]
|
||||
? (profile[attr] as string)
|
||||
: sanitizeHandle(profile.handle),
|
||||
)}`
|
||||
return (
|
||||
<InlineLinkText
|
||||
label={text}
|
||||
inner = (
|
||||
<TextLinkOnWebOnly
|
||||
type={type}
|
||||
style={style}
|
||||
lineHeight={1.2}
|
||||
numberOfLines={1}
|
||||
to={makeProfileLink(profile)}>
|
||||
<Text emoji style={style}>
|
||||
{text}
|
||||
</Text>
|
||||
</InlineLinkText>
|
||||
href={makeProfileLink(profile)}
|
||||
text={
|
||||
<Text emoji type={type} style={style} lineHeight={1.2}>
|
||||
{`${prefix || ''}${sanitizeDisplayName(
|
||||
typeof profile[attr] === 'string' && profile[attr]
|
||||
? (profile[attr] as string)
|
||||
: sanitizeHandle(profile.handle),
|
||||
)}`}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
inner = (
|
||||
<LoadingPlaceholder
|
||||
width={80}
|
||||
height={8}
|
||||
style={styles.loadingPlaceholder}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text
|
||||
return (
|
||||
<LoadingPlaceholder
|
||||
width={80}
|
||||
height={8}
|
||||
style={[a.relative, {top: 1, left: 2}]}
|
||||
/>
|
||||
)
|
||||
return inner
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loadingPlaceholder: {position: 'relative', top: 1, left: 2},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user