diff options
author | Eric Bailey <git@esb.lol> | 2025-02-18 09:45:46 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-18 09:45:46 -0600 |
commit | da45c42b6f1ebf0646b3327c9a4a39cd46ecb8d6 (patch) | |
tree | ee27741907d96f72601f6b9446be1e85f1b3396b | |
parent | 63ba0a436b7b2b768160ecd9e3971ed296602859 (diff) | |
download | voidsky-da45c42b6f1ebf0646b3327c9a4a39cd46ecb8d6.tar.zst |
[APP-1049] show label expiration in frontend (#7738)
* Add support for label exp to LabelsOnMeDialog * Add exp to PostAlerts, align with LabelsOnMe UI * Bump weight * Improve translations * Expiry should round up * Add a little visual alignment hack
-rw-r--r-- | src/components/moderation/LabelsOnMeDialog.tsx | 49 | ||||
-rw-r--r-- | src/components/moderation/ModerationDetailsDialog.tsx | 103 | ||||
-rw-r--r-- | src/lib/hooks/useTimeAgo.ts | 27 | ||||
-rw-r--r-- | src/lib/moderation/useModerationCauseDescription.ts | 15 |
4 files changed, 145 insertions, 49 deletions
diff --git a/src/components/moderation/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx index 7d1e7d032..de24729cf 100644 --- a/src/components/moderation/LabelsOnMeDialog.tsx +++ b/src/components/moderation/LabelsOnMeDialog.tsx @@ -5,6 +5,7 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useMutation} from '@tanstack/react-query' +import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useLabelSubject} from '#/lib/moderation' import {useLabelInfo} from '#/lib/moderation/useLabelInfo' import {makeProfileLink} from '#/lib/routes/links' @@ -66,7 +67,7 @@ function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { /> ) : ( <> - <Text style={[a.text_2xl, a.font_bold, a.pb_xs, a.leading_tight]}> + <Text style={[a.text_2xl, a.font_heavy, a.pb_xs, a.leading_tight]}> {isAccount ? ( <Trans>Labels on your account</Trans> ) : ( @@ -122,6 +123,7 @@ function Label({ const sourceName = labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src + const timeDiff = useGetTimeAgo({future: true}) return ( <View style={[ @@ -163,18 +165,41 @@ function Label({ <Trans>This label was applied by you.</Trans> </Text> ) : ( - <View style={{flexDirection: 'row'}}> - <Text style={[t.atoms.text_contrast_medium]}> - <Trans>Source: </Trans>{' '} + <View + style={[ + a.flex_row, + a.justify_between, + a.gap_xl, + {paddingBottom: 1}, + ]}> + <Text + style={[a.flex_1, a.leading_snug, t.atoms.text_contrast_medium]} + numberOfLines={1}> + <Trans> + Source:{' '} + <InlineLinkText + label={sourceName} + to={makeProfileLink( + labeler ? labeler.creator : {did: label.src, handle: ''}, + )} + onPress={() => control.close()}> + {sourceName} + </InlineLinkText> + </Trans> </Text> - <InlineLinkText - label={sourceName} - to={makeProfileLink( - labeler ? labeler.creator : {did: label.src, handle: ''}, - )} - onPress={() => control.close()}> - {sourceName} - </InlineLinkText> + {label.exp && ( + <View> + <Text + style={[ + a.leading_snug, + a.text_sm, + a.italic, + t.atoms.text_contrast_medium, + ]}> + <Trans>Expires in {timeDiff(Date.now(), label.exp)}</Trans> + </Text> + </View> + )} </View> )} </View> diff --git a/src/components/moderation/ModerationDetailsDialog.tsx b/src/components/moderation/ModerationDetailsDialog.tsx index bdbb2daa5..658ef48d1 100644 --- a/src/components/moderation/ModerationDetailsDialog.tsx +++ b/src/components/moderation/ModerationDetailsDialog.tsx @@ -3,14 +3,14 @@ import {ModerationCause} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {makeProfileLink} from '#/lib/routes/links' import {listUriToHref} from '#/lib/strings/url-helpers' import {isNative} from '#/platform/detection' import {useSession} from '#/state/session' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, useGutters, useTheme} from '#/alf' import * as Dialog from '#/components/Dialog' -import {Divider} from '#/components/Divider' import {InlineLinkText} from '#/components/Link' import {AppModerationCause} from '#/components/Pills' import {Text} from '#/components/Typography' @@ -38,9 +38,11 @@ function ModerationDetailsDialogInner({ control: Dialog.DialogOuterProps['control'] }) { const t = useTheme() + const xGutters = useGutters([0, 'base']) const {_} = useLingui() const desc = useModerationCauseDescription(modcause) const {currentAccount} = useSession() + const timeDiff = useGetTimeAgo({future: true}) let name let description @@ -128,37 +130,88 @@ function ModerationDetailsDialogInner({ description = '' } + const sourceName = + desc.source || desc.sourceDisplayName || _(msg`an unknown labeler`) + return ( - <Dialog.ScrollableInner label={_(msg`Moderation details`)}> - <Text emoji style={[t.atoms.text, a.text_2xl, a.font_bold, a.mb_sm]}> - {name} - </Text> - <Text style={[t.atoms.text, a.text_md, a.leading_snug]}> - {description} - </Text> + <Dialog.ScrollableInner + label={_(msg`Moderation details`)} + contentContainerStyle={{ + paddingLeft: 0, + paddingRight: 0, + paddingBottom: 0, + }}> + <View style={[xGutters, a.pb_lg]}> + <Text emoji style={[t.atoms.text, a.text_2xl, a.font_heavy, a.mb_sm]}> + {name} + </Text> + <Text style={[t.atoms.text, a.text_sm, a.leading_snug]}> + {description} + </Text> + </View> {modcause?.type === 'label' && ( - <View style={[a.pt_lg]}> - <Divider /> + <View + style={[ + xGutters, + a.py_md, + a.border_t, + !isNative && t.atoms.bg_contrast_25, + t.atoms.border_contrast_low, + { + borderBottomLeftRadius: a.rounded_md.borderRadius, + borderBottomRightRadius: a.rounded_md.borderRadius, + }, + ]}> {modcause.source.type === 'user' ? ( - <Text style={[t.atoms.text, a.text_md, a.leading_snug, a.mt_lg]}> + <Text style={[t.atoms.text, a.text_md, a.leading_snug]}> <Trans>This label was applied by the author.</Trans> </Text> ) : ( <> - <Text style={[t.atoms.text, a.text_md, a.leading_snug, a.mt_lg]}> - <Trans> - This label was applied by{' '} - <InlineLinkText - label={desc.source || _(msg`an unknown labeler`)} - to={makeProfileLink({did: modcause.label.src, handle: ''})} - onPress={() => control.close()} - style={a.text_md}> - {desc.source || _(msg`an unknown labeler`)} - </InlineLinkText> - . - </Trans> - </Text> + <View + style={[ + a.flex_row, + a.justify_between, + a.gap_xl, + {paddingBottom: 1}, + ]}> + <Text + style={[ + a.flex_1, + a.leading_snug, + t.atoms.text_contrast_medium, + ]} + numberOfLines={1}> + <Trans> + Source:{' '} + <InlineLinkText + label={sourceName} + to={makeProfileLink({ + did: modcause.label.src, + handle: '', + })} + onPress={() => control.close()}> + {sourceName} + </InlineLinkText> + </Trans> + </Text> + {modcause.label.exp && ( + <View> + <Text + style={[ + a.leading_snug, + a.text_sm, + a.italic, + t.atoms.text_contrast_medium, + ]}> + <Trans> + Expires in {timeDiff(Date.now(), modcause.label.exp)} + </Trans> + </Text> + </View> + )} + </View> </> )} </View> diff --git a/src/lib/hooks/useTimeAgo.ts b/src/lib/hooks/useTimeAgo.ts index 3a8bf49bc..7995ac824 100644 --- a/src/lib/hooks/useTimeAgo.ts +++ b/src/lib/hooks/useTimeAgo.ts @@ -19,7 +19,7 @@ const HOUR = MINUTE * 60 const DAY = HOUR * 24 const MONTH_30 = DAY * 30 -export function useGetTimeAgo() { +export function useGetTimeAgo({future = false}: {future?: boolean} = {}) { const {i18n} = useLingui() return useCallback( ( @@ -27,10 +27,10 @@ export function useGetTimeAgo() { later: number | string | Date, options?: {format: DateDiffFormat}, ) => { - const diff = dateDiff(earlier, later) + const diff = dateDiff(earlier, later, future ? 'up' : 'down') return formatDateDiff({diff, i18n, format: options?.format}) }, - [i18n], + [i18n, future], ) } @@ -45,6 +45,7 @@ export function useGetTimeAgo() { export function dateDiff( earlier: number | string | Date, later: number | string | Date, + rounding: 'up' | 'down' = 'down', ): DateDiff { let diff = { value: 0, @@ -65,25 +66,37 @@ export function dateDiff( unit: 'second' as DateDiff['unit'], } } else if (diffSeconds < HOUR) { - const value = Math.floor(diffSeconds / MINUTE) + const value = + rounding === 'up' + ? Math.ceil(diffSeconds / MINUTE) + : Math.floor(diffSeconds / MINUTE) diff = { value, unit: 'minute' as DateDiff['unit'], } } else if (diffSeconds < DAY) { - const value = Math.floor(diffSeconds / HOUR) + const value = + rounding === 'up' + ? Math.ceil(diffSeconds / HOUR) + : Math.floor(diffSeconds / HOUR) diff = { value, unit: 'hour' as DateDiff['unit'], } } else if (diffSeconds < MONTH_30) { - const value = Math.floor(diffSeconds / DAY) + const value = + rounding === 'up' + ? Math.ceil(diffSeconds / DAY) + : Math.floor(diffSeconds / DAY) diff = { value, unit: 'day' as DateDiff['unit'], } } else { - const value = Math.floor(diffSeconds / MONTH_30) + const value = + rounding === 'up' + ? Math.ceil(diffSeconds / MONTH_30) + : Math.floor(diffSeconds / MONTH_30) diff = { value, unit: 'month' as DateDiff['unit'], diff --git a/src/lib/moderation/useModerationCauseDescription.ts b/src/lib/moderation/useModerationCauseDescription.ts index 9dce0b565..9e3470077 100644 --- a/src/lib/moderation/useModerationCauseDescription.ts +++ b/src/lib/moderation/useModerationCauseDescription.ts @@ -7,6 +7,7 @@ import { import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {sanitizeHandle} from '#/lib/strings/handles' import {useLabelDefinitions} from '#/state/preferences' import {useSession} from '#/state/session' import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' @@ -23,6 +24,7 @@ export interface ModerationCauseDescription { name: string description: string source?: string + sourceDisplayName?: string sourceType?: ModerationCauseSource['type'] sourceAvi?: string sourceDid?: string @@ -130,14 +132,16 @@ export function useModerationCauseDescription( const def = cause.labelDef || getDefinition(labelDefs, cause.label) const strings = getLabelStrings(i18n.locale, globalLabelStrings, def) const labeler = labelers.find(l => l.creator.did === cause.label.src) - let source = - labeler?.creator.displayName || - (labeler?.creator.handle ? '@' + labeler?.creator.handle : undefined) + let source = labeler + ? sanitizeHandle(labeler.creator.handle, '@') + : undefined + let sourceDisplayName = labeler?.creator.displayName if (!source) { if (cause.label.src === BSKY_LABELER_DID) { - source = 'Bluesky Moderation Service' + source = 'moderation.bsky.app' + sourceDisplayName = 'Bluesky Moderation Service' } else { - source = cause.label.src + source = _(msg`an unknown labeler`) } } if (def.identifier === 'porn' || def.identifier === 'sexual') { @@ -154,6 +158,7 @@ export function useModerationCauseDescription( name: strings.name, description: strings.description, source, + sourceDisplayName, sourceType: cause.source.type, sourceAvi: labeler?.creator.avatar, sourceDid: cause.label.src, |