diff options
author | Eric Bailey <git@esb.lol> | 2024-02-26 22:33:48 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-26 20:33:48 -0800 |
commit | 58aaad704aa971c5ebbf5a5f330a2e2129b557f6 (patch) | |
tree | 74a448e61e83ca9292b0c6bf8d638bcfabd11eec /src/components/TagMenu/index.tsx | |
parent | c8582924e2421e5383050c4f60a80d2e74287c07 (diff) | |
download | voidsky-58aaad704aa971c5ebbf5a5f330a2e2129b557f6.tar.zst |
Add tags and mute words (#2968)
* Add bare minimum hashtags support (#2804) * Add bare minimum hashtags support As atproto/api already parses hashtags, this is as simple as hooking it up like link segments. This is "bare minimum" because: - Opening hashtag "#foo" is actually just a search for "foo" right now to work around #2491. - There is no integration in the composer. This hasn't stopped people from using hashtags already, and can be added later. - This change itself only had to hook things up - thank you for having already put the hashtag parsing in place. * Remove workaround for hash search not working now that it's fixed * Add RichTextTag and TagMenu * Sketch * Remove hackfix * Some cleanup * Sketch web * Mobile design * Mobile handling of tags search * Web only * Fix navigation woes * Use new callback * Hook it up * Integrate muted tags * Fix dropdown styles * Type error * Use close callback * Fix styles * Cleanup, install latest sdk * Quick muted words screen * Targets * Dir structure * Icons, list view * Move to dialog * Add removal confirmation * Swap copy * Improve checkboxees * Update matching, add tests * Moderate embeds * Create global dialogs concept again to prevent flashing * Add access from moderation screen * Highlight tags on native * Add web highlighting * Add close to web modal * Adjust close color * Rename toggles and adjust logic * Icon update * Load states * Improve regex * Improve regex * Improve regex * Revert link test * Hyphenated words * Improve matching * Enhance * Some tweaks * Muted words modal changes * Handle invalid handles, handle long tags * Remove main regex * Better test * Space/punct check drop to includes * Lowercase post text before comparison * Add better real world test case --------- Co-authored-by: Kisaragi Hiu <mail@kisaragi-hiu.com>
Diffstat (limited to 'src/components/TagMenu/index.tsx')
-rw-r--r-- | src/components/TagMenu/index.tsx | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/src/components/TagMenu/index.tsx b/src/components/TagMenu/index.tsx new file mode 100644 index 000000000..2fec7a188 --- /dev/null +++ b/src/components/TagMenu/index.tsx @@ -0,0 +1,279 @@ +import React from 'react' +import {View} from 'react-native' +import {useNavigation} from '@react-navigation/native' +import {useLingui} from '@lingui/react' +import {msg, Trans} from '@lingui/macro' + +import {atoms as a, native, useTheme} from '#/alf' +import * as Dialog from '#/components/Dialog' +import {Text} from '#/components/Typography' +import {Button, ButtonText} from '#/components/Button' +import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' +import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' +import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' +import {Divider} from '#/components/Divider' +import {Link} from '#/components/Link' +import {makeSearchLink} from '#/lib/routes/links' +import {NavigationProp} from '#/lib/routes/types' +import { + usePreferencesQuery, + useUpsertMutedWordsMutation, + useRemoveMutedWordMutation, +} from '#/state/queries/preferences' +import {Loader} from '#/components/Loader' +import {isInvalidHandle} from '#/lib/strings/handles' + +export function useTagMenuControl() { + return Dialog.useDialogControl() +} + +export function TagMenu({ + children, + control, + tag, + authorHandle, +}: React.PropsWithChildren<{ + control: Dialog.DialogOuterProps['control'] + tag: string + authorHandle?: string +}>) { + const {_} = useLingui() + const t = useTheme() + const navigation = useNavigation<NavigationProp>() + const {isLoading: isPreferencesLoading, data: preferences} = + usePreferencesQuery() + const { + mutateAsync: upsertMutedWord, + variables: optimisticUpsert, + reset: resetUpsert, + } = useUpsertMutedWordsMutation() + const { + mutateAsync: removeMutedWord, + variables: optimisticRemove, + reset: resetRemove, + } = useRemoveMutedWordMutation() + + const sanitizedTag = tag.replace(/^#/, '') + const isMuted = Boolean( + (preferences?.mutedWords?.find( + m => m.value === sanitizedTag && m.targets.includes('tag'), + ) ?? + optimisticUpsert?.find( + m => m.value === sanitizedTag && m.targets.includes('tag'), + )) && + !(optimisticRemove?.value === sanitizedTag), + ) + + return ( + <> + {children} + + <Dialog.Outer control={control}> + <Dialog.Handle /> + + <Dialog.Inner label={_(msg`Tag menu: ${tag}`)}> + {isPreferencesLoading ? ( + <View style={[a.w_full, a.align_center]}> + <Loader size="lg" /> + </View> + ) : ( + <> + <View + style={[ + a.rounded_md, + a.border, + a.mb_md, + t.atoms.border_contrast_low, + t.atoms.bg_contrast_25, + ]}> + <Link + label={_(msg`Search for all posts with tag ${tag}`)} + to={makeSearchLink({query: tag})} + onPress={e => { + e.preventDefault() + + control.close(() => { + // @ts-ignore :ron_swanson: "I know more than you" + navigation.navigate('SearchTab', { + screen: 'Search', + params: { + q: tag, + }, + }) + }) + + return false + }}> + <View + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.justify_start, + a.gap_md, + a.px_lg, + a.py_md, + ]}> + <Search size="lg" style={[t.atoms.text_contrast_medium]} /> + <Text + numberOfLines={1} + ellipsizeMode="middle" + style={[ + a.flex_1, + a.text_md, + a.font_bold, + native({top: 2}), + t.atoms.text_contrast_medium, + ]}> + <Trans> + See{' '} + <Text style={[a.text_md, a.font_bold, t.atoms.text]}> + {tag} + </Text>{' '} + posts + </Trans> + </Text> + </View> + </Link> + + {authorHandle && !isInvalidHandle(authorHandle) && ( + <> + <Divider /> + + <Link + label={_( + msg`Search for all posts by @${authorHandle} with tag ${tag}`, + )} + to={makeSearchLink({query: tag, from: authorHandle})} + onPress={e => { + e.preventDefault() + + control.close(() => { + // @ts-ignore :ron_swanson: "I know more than you" + navigation.navigate('SearchTab', { + screen: 'Search', + params: { + q: + tag + + (authorHandle ? ` from:${authorHandle}` : ''), + }, + }) + }) + + return false + }}> + <View + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.justify_start, + a.gap_md, + a.px_lg, + a.py_md, + ]}> + <Person + size="lg" + style={[t.atoms.text_contrast_medium]} + /> + <Text + numberOfLines={1} + ellipsizeMode="middle" + style={[ + a.flex_1, + a.text_md, + a.font_bold, + native({top: 2}), + t.atoms.text_contrast_medium, + ]}> + <Trans> + See{' '} + <Text + style={[a.text_md, a.font_bold, t.atoms.text]}> + {tag} + </Text>{' '} + posts by this user + </Trans> + </Text> + </View> + </Link> + </> + )} + + {preferences ? ( + <> + <Divider /> + + <Button + label={ + isMuted + ? _(msg`Unmute all ${tag} posts`) + : _(msg`Mute all ${tag} posts`) + } + onPress={() => { + control.close(() => { + if (isMuted) { + resetUpsert() + removeMutedWord({ + value: sanitizedTag, + targets: ['tag'], + }) + } else { + resetRemove() + upsertMutedWord([ + {value: sanitizedTag, targets: ['tag']}, + ]) + } + }) + }}> + <View + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.justify_start, + a.gap_md, + a.px_lg, + a.py_md, + ]}> + <Mute + size="lg" + style={[t.atoms.text_contrast_medium]} + /> + <Text + numberOfLines={1} + ellipsizeMode="middle" + style={[ + a.flex_1, + a.text_md, + a.font_bold, + native({top: 2}), + t.atoms.text_contrast_medium, + ]}> + {isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '} + <Text style={[a.text_md, a.font_bold, t.atoms.text]}> + {tag} + </Text>{' '} + <Trans>posts</Trans> + </Text> + </View> + </Button> + </> + ) : null} + </View> + + <Button + label={_(msg`Close this dialog`)} + size="small" + variant="ghost" + color="secondary" + onPress={() => control.close()}> + <ButtonText>Cancel</ButtonText> + </Button> + </> + )} + </Dialog.Inner> + </Dialog.Outer> + </> + ) +} |