diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-05-31 18:43:04 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-31 10:43:04 -0500 |
commit | 22e1eb18c81b6f41927bc86d4726223c2634e19e (patch) | |
tree | 1fbd17678b2922667affa895270dff1f634216e0 /src | |
parent | 8eb3cebb362cc438e368a9fbb78e2f85403ffeed (diff) | |
download | voidsky-22e1eb18c81b6f41927bc86d4726223c2634e19e.tar.zst |
[🐴] Record message (#4230)
* send record via link in text * re-trim text after removing link * record message * only show copy text if message + add translate * reduce padding * adjust padding * Tweak spacing * Stop clickthrough for hidden content * Update bg to show labels --------- Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/dms/MessageItem.tsx | 74 | ||||
-rw-r--r-- | src/components/dms/MessageItemEmbed.tsx | 109 | ||||
-rw-r--r-- | src/components/dms/MessageMenu.tsx | 47 | ||||
-rw-r--r-- | src/components/moderation/ContentHider.tsx | 17 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/QuoteEmbed.tsx | 18 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/index.tsx | 18 |
6 files changed, 225 insertions, 58 deletions
diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index c5ff81091..b498ddf1c 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -6,7 +6,11 @@ import { TextStyle, View, } from 'react-native' -import {ChatBskyConvoDefs, RichText as RichTextAPI} from '@atproto/api' +import { + AppBskyEmbedRecord, + ChatBskyConvoDefs, + RichText as RichTextAPI, +} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -18,6 +22,7 @@ import {ActionsWrapper} from '#/components/dms/ActionsWrapper' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {RichText} from '../RichText' +import {MessageItemEmbed} from './MessageItemEmbed' let MessageItem = ({ item, @@ -77,37 +82,44 @@ let MessageItem = ({ return ( <View style={[isFromSelf ? a.mr_md : a.ml_md]}> <ActionsWrapper isFromSelf={isFromSelf} message={message}> - <View - style={[ - a.py_sm, - a.my_2xs, - a.rounded_md, - { - paddingLeft: 14, - paddingRight: 14, - backgroundColor: isFromSelf - ? isPending - ? pendingColor - : t.palette.primary_500 - : t.palette.contrast_50, - borderRadius: 17, - }, - isFromSelf - ? {borderBottomRightRadius: isLastInGroup ? 2 : 17} - : {borderBottomLeftRadius: isLastInGroup ? 2 : 17}, - ]}> - <RichText - value={rt} + {AppBskyEmbedRecord.isMain(message.embed) && ( + <MessageItemEmbed embed={message.embed} /> + )} + {rt.text.length > 0 && ( + <View style={[ - a.text_md, - a.leading_snug, - isFromSelf && {color: t.palette.white}, - isPending && t.name !== 'light' && {color: t.palette.primary_300}, - ]} - interactiveStyle={a.underline} - enableTags - /> - </View> + a.py_sm, + a.my_2xs, + a.rounded_md, + { + paddingLeft: 14, + paddingRight: 14, + backgroundColor: isFromSelf + ? isPending + ? pendingColor + : t.palette.primary_500 + : t.palette.contrast_50, + borderRadius: 17, + }, + isFromSelf ? a.self_end : a.self_start, + isFromSelf + ? {borderBottomRightRadius: isLastInGroup ? 2 : 17} + : {borderBottomLeftRadius: isLastInGroup ? 2 : 17}, + ]}> + <RichText + value={rt} + style={[ + a.text_md, + a.leading_snug, + isFromSelf && {color: t.palette.white}, + isPending && + t.name !== 'light' && {color: t.palette.primary_300}, + ]} + interactiveStyle={a.underline} + enableTags + /> + </View> + )} </ActionsWrapper> {isLastInGroup && ( diff --git a/src/components/dms/MessageItemEmbed.tsx b/src/components/dms/MessageItemEmbed.tsx new file mode 100644 index 000000000..d64563b91 --- /dev/null +++ b/src/components/dms/MessageItemEmbed.tsx @@ -0,0 +1,109 @@ +import React, {useMemo} from 'react' +import {View} from 'react-native' +import { + AppBskyEmbedRecord, + AppBskyFeedPost, + AtUri, + RichText as RichTextAPI, +} from '@atproto/api' + +import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' +import {makeProfileLink} from '#/lib/routes/links' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {usePostQuery} from '#/state/queries/post' +import {PostEmbeds} from '#/view/com/util/post-embeds' +import {PostMeta} from '#/view/com/util/PostMeta' +import {atoms as a, useTheme} from '#/alf' +import {Link} from '#/components/Link' +import {ContentHider} from '#/components/moderation/ContentHider' +import {PostAlerts} from '#/components/moderation/PostAlerts' +import {RichText} from '#/components/RichText' + +let MessageItemEmbed = ({ + embed, +}: { + embed: AppBskyEmbedRecord.Main +}): React.ReactNode => { + const t = useTheme() + const {data: post} = usePostQuery(embed.record.uri) + + const moderationOpts = useModerationOpts() + const moderation = useMemo( + () => + moderationOpts && post ? moderatePost(post, moderationOpts) : undefined, + [moderationOpts, post], + ) + + const {rt, record} = useMemo(() => { + if ( + post && + AppBskyFeedPost.isRecord(post.record) && + AppBskyFeedPost.validateRecord(post.record).success + ) { + return { + rt: new RichTextAPI({ + text: post.record.text, + facets: post.record.facets, + }), + record: post.record, + } + } + + return {rt: undefined, record: undefined} + }, [post]) + + if (!post || !moderation || !rt || !record) { + return null + } + + const itemUrip = new AtUri(post.uri) + const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey) + + return ( + <Link to={itemHref}> + <View + style={[ + a.w_full, + t.atoms.bg, + t.atoms.border_contrast_low, + a.rounded_md, + a.border, + a.p_md, + a.my_xs, + ]}> + <PostMeta + showAvatar + author={post.author} + moderation={moderation} + authorHasWarning={!!post.author.labels?.length} + timestamp={post.indexedAt} + postHref={itemHref} + /> + <ContentHider modui={moderation.ui('contentView')}> + <PostAlerts modui={moderation.ui('contentView')} style={a.py_xs} /> + {rt.text && ( + <View style={a.mt_xs}> + <RichText + enableTags + testID="postText" + value={rt} + style={[a.text_sm, t.atoms.text_contrast_high]} + authorHandle={post.author.handle} + /> + </View> + )} + {post.embed && ( + <PostEmbeds + embed={post.embed} + moderation={moderation} + style={a.mt_xs} + quoteTextStyle={[a.text_sm, t.atoms.text_contrast_high]} + /> + )} + </ContentHider> + </View> + </Link> + ) +} +MessageItemEmbed = React.memo(MessageItemEmbed) +export {MessageItemEmbed} diff --git a/src/components/dms/MessageMenu.tsx b/src/components/dms/MessageMenu.tsx index 21812d268..92913d1cb 100644 --- a/src/components/dms/MessageMenu.tsx +++ b/src/components/dms/MessageMenu.tsx @@ -6,12 +6,16 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {richTextToString} from '#/lib/strings/rich-text-helpers' +import {getTranslatorLink} from '#/locale/helpers' +import {useLanguagePrefs} from '#/state/preferences' +import {useOpenLink} from '#/state/preferences/in-app-browser' import {isWeb} from 'platform/detection' import {useConvoActive} from 'state/messages/convo' import {useSession} from 'state/session' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {ReportDialog} from '#/components/dms/ReportDialog' +import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' @@ -35,10 +39,12 @@ export let MessageMenu = ({ const convo = useConvoActive() const deleteControl = usePromptControl() const reportControl = usePromptControl() + const langPrefs = useLanguagePrefs() + const openLink = useOpenLink() const isFromSelf = message.sender?.did === currentAccount?.did - const onCopyPostText = React.useCallback(() => { + const onCopyMessage = React.useCallback(() => { const str = richTextToString( new RichText({ text: message.text, @@ -51,6 +57,14 @@ export let MessageMenu = ({ Toast.show(_(msg`Copied to clipboard`)) }, [_, message.text, message.facets]) + const onPressTranslateMessage = React.useCallback(() => { + const translatorUrl = getTranslatorLink( + message.text, + langPrefs.primaryLanguage, + ) + openLink(translatorUrl) + }, [langPrefs.primaryLanguage, message.text, openLink]) + const onDelete = React.useCallback(() => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) convo @@ -81,16 +95,27 @@ export let MessageMenu = ({ )} <Menu.Outer> - <Menu.Group> - <Menu.Item - testID="messageDropdownCopyBtn" - label={_(msg`Copy message text`)} - onPress={onCopyPostText}> - <Menu.ItemText>{_(msg`Copy message text`)}</Menu.ItemText> - <Menu.ItemIcon icon={ClipboardIcon} position="right" /> - </Menu.Item> - </Menu.Group> - <Menu.Divider /> + {message.text.length > 0 && ( + <> + <Menu.Group> + <Menu.Item + testID="messageDropdownTranslateBtn" + label={_(msg`Translate`)} + onPress={onPressTranslateMessage}> + <Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText> + <Menu.ItemIcon icon={Translate} position="right" /> + </Menu.Item> + <Menu.Item + testID="messageDropdownCopyBtn" + label={_(msg`Copy message text`)} + onPress={onCopyMessage}> + <Menu.ItemText>{_(msg`Copy message text`)}</Menu.ItemText> + <Menu.ItemIcon icon={ClipboardIcon} position="right" /> + </Menu.Item> + </Menu.Group> + <Menu.Divider /> + </> + )} <Menu.Group> <Menu.Item testID="messageDropdownDeleteBtn" diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx index 1e8f36d31..fd71ec838 100644 --- a/src/components/moderation/ContentHider.tsx +++ b/src/components/moderation/ContentHider.tsx @@ -1,20 +1,19 @@ import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {ModerationUI} from '@atproto/api' -import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {isJustAMute} from '#/lib/moderation' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {sanitizeDisplayName} from '#/lib/strings/display-names' - -import {atoms as a, useTheme, useBreakpoints, web} from '#/alf' +import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {Button} from '#/components/Button' -import {Text} from '#/components/Typography' import { ModerationDetailsDialog, useModerationDetailsDialogControl, } from '#/components/moderation/ModerationDetailsDialog' +import {Text} from '#/components/Typography' export function ContentHider({ testID, @@ -52,7 +51,9 @@ export function ContentHider({ <ModerationDetailsDialog control={control} modcause={blur} /> <Button - onPress={() => { + onPress={e => { + e.preventDefault() + e.stopPropagation() if (!modui.noOverride) { setOverride(v => !v) } else { @@ -121,7 +122,9 @@ export function ContentHider({ {desc.source && blur.type === 'label' && !override && ( <Button - onPress={() => { + onPress={e => { + e.preventDefault() + e.stopPropagation() control.open() }} label={_( diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index 019216eca..cdbdafc9b 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -2,6 +2,7 @@ import React from 'react' import { StyleProp, StyleSheet, + TextStyle, TouchableOpacity, View, ViewStyle, @@ -31,7 +32,7 @@ import {InfoCircleIcon} from 'lib/icons' import {makeProfileLink} from 'lib/routes/links' import {precacheProfile} from 'state/queries/profile' import {ComposerOptsQuote} from 'state/shell/composer' -import {atoms as a} from '#/alf' +import {atoms as a, flatten} from '#/alf' import {RichText} from '#/components/RichText' import {ContentHider} from '../../../../components/moderation/ContentHider' import {PostAlerts} from '../../../../components/moderation/PostAlerts' @@ -45,10 +46,12 @@ export function MaybeQuoteEmbed({ embed, onOpen, style, + textStyle, }: { embed: AppBskyEmbedRecord.View onOpen?: () => void style?: StyleProp<ViewStyle> + textStyle?: StyleProp<TextStyle> }) { const pal = usePalette('default') if ( @@ -62,6 +65,7 @@ export function MaybeQuoteEmbed({ postRecord={embed.record.value} onOpen={onOpen} style={style} + textStyle={textStyle} /> ) } else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) { @@ -91,11 +95,13 @@ function QuoteEmbedModerated({ postRecord, onOpen, style, + textStyle, }: { viewRecord: AppBskyEmbedRecord.ViewRecord postRecord: AppBskyFeedPost.Record onOpen?: () => void style?: StyleProp<ViewStyle> + textStyle?: StyleProp<TextStyle> }) { const moderationOpts = useModerationOpts() const moderation = React.useMemo(() => { @@ -120,6 +126,7 @@ function QuoteEmbedModerated({ moderation={moderation} onOpen={onOpen} style={style} + textStyle={textStyle} /> ) } @@ -129,11 +136,13 @@ export function QuoteEmbed({ moderation, onOpen, style, + textStyle, }: { quote: ComposerOptsQuote moderation?: ModerationDecision onOpen?: () => void style?: StyleProp<ViewStyle> + textStyle?: StyleProp<TextStyle> }) { const queryClient = useQueryClient() const pal = usePalette('default') @@ -192,7 +201,7 @@ export function QuoteEmbed({ {richText ? ( <RichText value={richText} - style={[a.text_md]} + style={[a.text_md, flatten(textStyle)]} numberOfLines={20} disableLinks /> @@ -250,11 +259,6 @@ const styles = StyleSheet.create({ paddingHorizontal: 12, borderWidth: hairlineWidth, }, - quotePost: { - flex: 1, - paddingLeft: 13, - paddingRight: 8, - }, errorContainer: { flexDirection: 'row', alignItems: 'center', diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index d8ef6e6ab..962f3d8c5 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -4,6 +4,7 @@ import { StyleProp, StyleSheet, Text, + TextStyle, View, ViewStyle, } from 'react-native' @@ -41,11 +42,13 @@ export function PostEmbeds({ moderation, onOpen, style, + quoteTextStyle, }: { embed?: Embed moderation?: ModerationDecision onOpen?: () => void style?: StyleProp<ViewStyle> + quoteTextStyle?: StyleProp<TextStyle> }) { const pal = usePalette('default') const {openLightbox} = useLightboxControls() @@ -60,7 +63,11 @@ export function PostEmbeds({ moderation={moderation} onOpen={onOpen} /> - <MaybeQuoteEmbed embed={embed.record} onOpen={onOpen} /> + <MaybeQuoteEmbed + embed={embed.record} + onOpen={onOpen} + textStyle={quoteTextStyle} + /> </View> ) } @@ -87,7 +94,14 @@ export function PostEmbeds({ // quote post // = - return <MaybeQuoteEmbed embed={embed} style={style} onOpen={onOpen} /> + return ( + <MaybeQuoteEmbed + embed={embed} + style={style} + textStyle={quoteTextStyle} + onOpen={onOpen} + /> + ) } // image embed |