diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/icons/CalendarClock.tsx | 5 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 233 |
2 files changed, 170 insertions, 68 deletions
diff --git a/src/components/icons/CalendarClock.tsx b/src/components/icons/CalendarClock.tsx new file mode 100644 index 000000000..52ba8094e --- /dev/null +++ b/src/components/icons/CalendarClock.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const CalendarClock_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M15.439 3.148a1 1 0 0 1 .41.645l.568 3.22a7 7 0 1 1-6.174 10.97L4.32 19.027a1 1 0 0 1-1.159-.811L1.078 6.398a1 1 0 0 1 .81-1.158l12.803-2.258a1 1 0 0 1 .748.166ZM9.325 16.114A7 7 0 0 1 9 14c0-1.56.51-3 1.372-4.164l-6.456 1.139 1.041 5.909 4.368-.77ZM3.568 9.005l10.833-1.91-.347-1.97L3.22 7.036l.347 1.97ZM16 9a5 5 0 1 0 0 10 5 5 0 0 0 0-10Zm0 2a1 1 0 0 1 1 1v1.586l1.374 1.374a1 1 0 0 1-1.414 1.414l-1.667-1.667A1 1 0 0 1 15 14v-2a1 1 0 0 1 1-1Z', +}) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 5044f9621..9edca4335 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -1,5 +1,5 @@ import React, {memo, useMemo} from 'react' -import {StyleSheet, View} from 'react-native' +import {StyleSheet, Text as RNText, View} from 'react-native' import { AppBskyFeedDefs, AppBskyFeedPost, @@ -8,7 +8,6 @@ import { ModerationDecision, RichText as RichTextAPI, } from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -21,6 +20,7 @@ import {sanitizeHandle} from '#/lib/strings/handles' import {countLines} from '#/lib/strings/helpers' import {niceDate} from '#/lib/strings/time' import {s} from '#/lib/styles' +import {getTranslatorLink, isPostInLanguage} from '#/locale/helpers' import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow' import {useLanguagePrefs} from '#/state/preferences' import {ThreadPost} from '#/state/queries/post-thread' @@ -28,26 +28,30 @@ import {useSession} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn' +import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' +import {Link, TextLink} from '#/view/com/util/Link' +import {formatCount} from '#/view/com/util/numeric/format' +import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls' +import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds' +import {PostMeta} from '#/view/com/util/PostMeta' +import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' +import {colors} from '#/components/Admonition' +import {Button} from '#/components/Button' +import {CalendarClock_Stroke2_Corner0_Rounded as CalendarClockIcon} from '#/components/icons/CalendarClock' +import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' +import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import {InlineLinkText} from '#/components/Link' +import {ContentHider} from '#/components/moderation/ContentHider' +import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' +import {PostAlerts} from '#/components/moderation/PostAlerts' +import {PostHider} from '#/components/moderation/PostHider' import {AppModerationCause} from '#/components/Pills' +import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {SubtleWebHover} from '#/components/SubtleWebHover' -import {Text as NewText} from '#/components/Typography' -import {ContentHider} from '../../../components/moderation/ContentHider' -import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' -import {PostAlerts} from '../../../components/moderation/PostAlerts' -import {PostHider} from '../../../components/moderation/PostHider' -import {WhoCanReply} from '../../../components/WhoCanReply' -import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {Link, TextLink} from '../util/Link' -import {formatCount} from '../util/numeric/format' -import {PostCtrls} from '../util/post-ctrls/PostCtrls' -import {PostEmbeds, PostEmbedViewContext} from '../util/post-embeds' -import {PostMeta} from '../util/PostMeta' -import {Text} from '../util/text/Text' -import {PreviewableUserAvatar} from '../util/UserAvatar' +import {Text} from '#/components/Typography' +import {WhoCanReply} from '#/components/WhoCanReply' export function PostThreadItem({ post, @@ -125,19 +129,20 @@ export function PostThreadItem({ } function PostThreadItemDeleted({hideTopBorder}: {hideTopBorder?: boolean}) { - const pal = usePalette('default') + const t = useTheme() return ( <View style={[ - styles.outer, - pal.border, - pal.view, - s.p20, - s.flexRow, - hideTopBorder && styles.noTopBorder, + t.atoms.bg, + t.atoms.border_contrast_low, + a.p_xl, + a.pl_lg, + a.flex_row, + a.gap_md, + !hideTopBorder && a.border_t, ]}> - <FontAwesomeIcon icon={['far', 'trash-can']} color={pal.colors.icon} /> - <Text style={[pal.textLight, s.ml10]}> + <TrashIcon style={[t.atoms.text]} /> + <Text style={[t.atoms.text_contrast_medium, a.mt_2xs]}> <Trans>This post has been deleted.</Trans> </Text> </View> @@ -308,7 +313,7 @@ let PostThreadItemLoaded = ({ /> <View style={[a.flex_1]}> <Link style={s.flex1} href={authorHref} title={authorTitle}> - <NewText + <Text emoji style={[a.text_lg, a.font_bold, a.leading_snug, a.self_start]} numberOfLines={1}> @@ -317,10 +322,10 @@ let PostThreadItemLoaded = ({ sanitizeHandle(post.author.handle), moderation.ui('displayName'), )} - </NewText> + </Text> </Link> <Link style={s.flex1} href={authorHref} title={authorTitle}> - <NewText + <Text emoji style={[ a.text_md, @@ -329,7 +334,7 @@ let PostThreadItemLoaded = ({ ]} numberOfLines={1}> {sanitizeHandle(post.author.handle, '@')} - </NewText> + </Text> </Link> </View> {currentAccount?.did !== post.author.did && ( @@ -393,48 +398,48 @@ let PostThreadItemLoaded = ({ ]}> {post.repostCount != null && post.repostCount !== 0 ? ( <Link href={repostsHref} title={repostsTitle}> - <NewText + <Text testID="repostCount-expanded" style={[a.text_md, t.atoms.text_contrast_medium]}> - <NewText style={[a.text_md, a.font_bold, t.atoms.text]}> + <Text style={[a.text_md, a.font_bold, t.atoms.text]}> {formatCount(i18n, post.repostCount)} - </NewText>{' '} + </Text>{' '} <Plural value={post.repostCount} one="repost" other="reposts" /> - </NewText> + </Text> </Link> ) : null} {post.quoteCount != null && post.quoteCount !== 0 && !post.viewer?.embeddingDisabled ? ( <Link href={quotesHref} title={quotesTitle}> - <NewText + <Text testID="quoteCount-expanded" style={[a.text_md, t.atoms.text_contrast_medium]}> - <NewText style={[a.text_md, a.font_bold, t.atoms.text]}> + <Text style={[a.text_md, a.font_bold, t.atoms.text]}> {formatCount(i18n, post.quoteCount)} - </NewText>{' '} + </Text>{' '} <Plural value={post.quoteCount} one="quote" other="quotes" /> - </NewText> + </Text> </Link> ) : null} {post.likeCount != null && post.likeCount !== 0 ? ( <Link href={likesHref} title={likesTitle}> - <NewText + <Text testID="likeCount-expanded" style={[a.text_md, t.atoms.text_contrast_medium]}> - <NewText style={[a.text_md, a.font_bold, t.atoms.text]}> + <Text style={[a.text_md, a.font_bold, t.atoms.text]}> {formatCount(i18n, post.likeCount)} - </NewText>{' '} + </Text>{' '} <Plural value={post.likeCount} one="like" other="likes" /> - </NewText> + </Text> </Link> ) : null} </View> @@ -617,13 +622,13 @@ let PostThreadItemLoaded = ({ href={postHref} title={itemTitle} noFeedback> - <Text type="sm-medium" style={pal.textLight}> + <Text + style={[t.atoms.text_contrast_medium, a.font_bold, a.text_sm]}> <Trans>More</Trans> </Text> - <FontAwesomeIcon - icon="angle-right" - color={pal.colors.textLight} - size={14} + <ChevronRightIcon + size="xs" + style={[t.atoms.text_contrast_medium]} /> </Link> ) : undefined} @@ -732,32 +737,124 @@ function ExpandedPostDetails({ }, [openLink, translatorUrl]) return ( - <View style={[a.flex_row, a.align_center, a.flex_wrap, a.gap_sm, a.pt_md]}> - <NewText style={[a.text_sm, t.atoms.text_contrast_medium]}> - {niceDate(i18n, post.indexedAt)} - </NewText> - {isRootPost && ( - <WhoCanReply post={post} isThreadAuthor={isThreadAuthor} /> - )} - {needsTranslation && ( - <> - <NewText style={[a.text_sm, t.atoms.text_contrast_medium]}> - · - </NewText> + <View style={[a.gap_md, a.pt_md, a.align_start]}> + <BackdatedPostIndicator post={post} /> + <View style={[a.flex_row, a.align_center, a.flex_wrap, a.gap_sm]}> + <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> + {niceDate(i18n, post.indexedAt)} + </Text> + {isRootPost && ( + <WhoCanReply post={post} isThreadAuthor={isThreadAuthor} /> + )} + {needsTranslation && ( + <> + <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> + · + </Text> - <InlineLinkText - to="#" - label={_(msg`Translate`)} - style={[a.text_sm, pal.link]} - onPress={onTranslatePress}> - <Trans>Translate</Trans> - </InlineLinkText> - </> - )} + <InlineLinkText + to="#" + label={_(msg`Translate`)} + style={[a.text_sm, pal.link]} + onPress={onTranslatePress}> + <Trans>Translate</Trans> + </InlineLinkText> + </> + )} + </View> </View> ) } +function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) { + const t = useTheme() + const {_, i18n} = useLingui() + const control = Prompt.usePromptControl() + + const indexedAt = new Date(post.indexedAt) + const createdAt = AppBskyFeedPost.isRecord(post.record) + ? new Date(post.record.createdAt) + : new Date(post.indexedAt) + + // backdated if createdAt is 24 hours or more before indexedAt + const isBackdated = + indexedAt.getTime() - createdAt.getTime() > 24 * 60 * 60 * 1000 + + if (!isBackdated) return null + + const orange = t.name === 'light' ? colors.warning.dark : colors.warning.light + + return ( + <> + <Button + label={_(msg`Archived post`)} + accessibilityHint={_( + msg`Show information about when this post was created`, + )} + onPress={e => { + e.preventDefault() + e.stopPropagation() + control.open() + }}> + {({hovered, pressed}) => ( + <View + style={[ + a.flex_row, + a.align_center, + a.rounded_full, + t.atoms.bg_contrast_25, + (hovered || pressed) && t.atoms.bg_contrast_50, + { + gap: 3, + paddingHorizontal: 6, + paddingVertical: 3, + }, + ]}> + <CalendarClockIcon fill={orange} size="sm" aria-hidden /> + <Text + style={[ + a.text_xs, + a.font_bold, + a.leading_tight, + t.atoms.text_contrast_medium, + ]}> + <Trans>Archived from {niceDate(i18n, createdAt)}</Trans> + </Text> + </View> + )} + </Button> + + <Prompt.Outer control={control}> + <Prompt.TitleText> + <Trans>Archived post</Trans> + </Prompt.TitleText> + <Prompt.DescriptionText> + <Trans> + This post claims to have been created on{' '} + <RNText style={[a.font_bold]}>{niceDate(i18n, createdAt)}</RNText>, + but was first seen by Bluesky on{' '} + <RNText style={[a.font_bold]}>{niceDate(i18n, indexedAt)}</RNText>. + </Trans> + </Prompt.DescriptionText> + <Text + style={[ + a.text_md, + a.leading_snug, + t.atoms.text_contrast_high, + a.pb_xl, + ]}> + <Trans> + Bluesky cannot confirm the authenticity of the claimed date. + </Trans> + </Text> + <Prompt.Actions> + <Prompt.Action cta={_(msg`Okay`)} onPress={() => {}} /> + </Prompt.Actions> + </Prompt.Outer> + </> + ) +} + function getThreadAuthor( post: AppBskyFeedDefs.PostView, record: AppBskyFeedPost.Record, |