From 39d324ab8bd99ca0e19f3e8f4dea5a61d54e6bb4 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 29 Feb 2024 15:23:28 -0800 Subject: Fix link warnings (#3058) * fix problems where www.bsky.app shows as a potential danger * never default to disabling warning * remove more defaults * update storybook cases * oops * reverse --- src/components/RichText.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/components/RichText.tsx') diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 3d5f08026..5d82d7e5e 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -105,8 +105,7 @@ export function RichText({ to={link.uri} style={[...styles, {pointerEvents: 'auto'}]} // @ts-ignore TODO - dataSet={WORD_WRAP} - warnOnMismatchingLabel> + dataSet={WORD_WRAP}> {toShortUrl(segment.text)} , ) -- cgit 1.4.1 From cf8b03801fd8e98bcd2da4d099a9dfbf5876de7d Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 29 Feb 2024 17:56:29 -0800 Subject: Dedicated screen for hashtags, POC ALF list (#3047) * create dedicated hashtag "search" screen clarify loading component name more adjustments rework `ViewHeader` to keep chevron centered w/ first line adjustments adjustments use `author` instead of `handle` in route add web route for url add web route for url Add desktop list header support web keep header lowercase add optional subtitle to view header correct isFetching logic oops use `isFetching` for clarity in footer combine logic update bskyweb finish screen style, add footer, add spinner, etc add list add header, params create a screen * add variable to server path * localize `By` * add empty state * more adjustments * sanitize author * fix web * add custom message for hashtag not found error * ellipsis in middle * fix * fix trans * account for multiple # * encode # * replaceall * Use sanitized tag * don't call function in lingui * add share button --------- Co-authored-by: Eric Bailey --- bskyweb/cmd/bskyweb/server.go | 1 + src/Navigation.tsx | 6 + src/components/Lists.tsx | 228 +++++++++++++++++++++++++++++++++++ src/components/RichText.tsx | 11 +- src/components/TagMenu/index.tsx | 54 ++++----- src/components/TagMenu/index.web.tsx | 25 ++-- src/lib/routes/types.ts | 3 + src/routes.ts | 1 + src/screens/Hashtag.tsx | 157 ++++++++++++++++++++++++ src/view/com/util/ViewHeader.tsx | 97 +++++++++------ 10 files changed, 502 insertions(+), 81 deletions(-) create mode 100644 src/components/Lists.tsx create mode 100644 src/screens/Hashtag.tsx (limited to 'src/components/RichText.tsx') diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index f13d568b7..6b76acc94 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -180,6 +180,7 @@ func serve(cctx *cli.Context) error { e.GET("/", server.WebHome) // generic routes + e.GET("/hashtag/:tag", server.WebGeneric) e.GET("/search", server.WebGeneric) e.GET("/feeds", server.WebGeneric) e.GET("/notifications", server.WebGeneric) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 0aeeeb6ad..c650c1f40 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -77,6 +77,7 @@ import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbed import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth' import {msg} from '@lingui/macro' import {i18n, MessageDescriptor} from '@lingui/core' +import HashtagScreen from '#/screens/Hashtag' const navigationRef = createNavigationContainerRef() @@ -262,6 +263,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { requireAuth: true, }} /> + HashtagScreen} + options={{title: title(msg`Hashtag`)}} + /> ) } diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx new file mode 100644 index 000000000..cf00734f0 --- /dev/null +++ b/src/components/Lists.tsx @@ -0,0 +1,228 @@ +import React from 'react' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {View} from 'react-native' +import {Loader} from '#/components/Loader' +import {Trans} from '@lingui/macro' +import {cleanError} from 'lib/strings/errors' +import {Button} from '#/components/Button' +import {Text} from '#/components/Typography' +import {StackActions} from '@react-navigation/native' +import {useNavigation} from '@react-navigation/core' +import {NavigationProp} from 'lib/routes/types' + +export function ListFooter({ + isFetching, + isError, + error, + onRetry, +}: { + isFetching: boolean + isError: boolean + error?: string + onRetry?: () => Promise +}) { + const t = useTheme() + + return ( + + {isFetching ? ( + + ) : ( + + )} + + ) +} + +function ListFooterMaybeError({ + isError, + error, + onRetry, +}: { + isError: boolean + error?: string + onRetry?: () => Promise +}) { + const t = useTheme() + + if (!isError) return null + + return ( + + + + {error ? ( + cleanError(error) + ) : ( + Oops, something went wrong! + )} + + + + + ) +} + +export function ListHeaderDesktop({ + title, + subtitle, +}: { + title: string + subtitle?: string +}) { + const {gtTablet} = useBreakpoints() + const t = useTheme() + + if (!gtTablet) return null + + return ( + + {title} + {subtitle ? ( + + {subtitle} + + ) : undefined} + + ) +} + +export function ListMaybePlaceholder({ + isLoading, + isEmpty, + isError, + empty, + error, + onRetry, +}: { + isLoading: boolean + isEmpty: boolean + isError: boolean + empty?: string + error?: string + onRetry?: () => Promise +}) { + const navigation = useNavigation() + const t = useTheme() + + const canGoBack = navigation.canGoBack() + const onGoBack = React.useCallback(() => { + if (canGoBack) { + navigation.goBack() + } else { + navigation.navigate('HomeTab') + navigation.dispatch(StackActions.popToTop()) + } + }, [navigation, canGoBack]) + + if (!isEmpty) return null + + return ( + + {isLoading ? ( + + + + ) : ( + <> + + + {isError ? ( + Oops! + ) : isEmpty ? ( + Page not found + ) : undefined} + + + {isError ? ( + + {error ? error : Something went wrong!} + + ) : isEmpty ? ( + + {empty ? ( + empty + ) : ( + + We're sorry! We can't find the page you were looking for. + + )} + + ) : undefined} + + + {isError && onRetry && ( + + )} + + + + )} + + ) +} diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 5d82d7e5e..1a14415cf 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -120,6 +120,7 @@ export function RichText({ - {tag} + {text} diff --git a/src/components/TagMenu/index.tsx b/src/components/TagMenu/index.tsx index c18c0d6a2..c9ced9a54 100644 --- a/src/components/TagMenu/index.tsx +++ b/src/components/TagMenu/index.tsx @@ -34,6 +34,10 @@ export function TagMenu({ authorHandle, }: React.PropsWithChildren<{ control: Dialog.DialogOuterProps['control'] + /** + * This should be the sanitized tag value from the facet itself, not the + * "display" value with a leading `#`. + */ tag: string authorHandle?: string }>) { @@ -52,16 +56,16 @@ export function TagMenu({ variables: optimisticRemove, reset: resetRemove, } = useRemoveMutedWordMutation() + const displayTag = '#' + tag - const sanitizedTag = tag.replace(/^#/, '') const isMuted = Boolean( (preferences?.mutedWords?.find( - m => m.value === sanitizedTag && m.targets.includes('tag'), + m => m.value === tag && m.targets.includes('tag'), ) ?? optimisticUpsert?.find( - m => m.value === sanitizedTag && m.targets.includes('tag'), + m => m.value === tag && m.targets.includes('tag'), )) && - !(optimisticRemove?.value === sanitizedTag), + !(optimisticRemove?.value === tag), ) return ( @@ -71,7 +75,7 @@ export function TagMenu({ - + {isPreferencesLoading ? ( @@ -87,18 +91,14 @@ export function TagMenu({ t.atoms.bg_contrast_25, ]}> { e.preventDefault() control.close(() => { - // @ts-ignore :ron_swanson: "I know more than you" - navigation.navigate('SearchTab', { - screen: 'Search', - params: { - q: tag, - }, + navigation.push('Hashtag', { + tag: tag.replaceAll('#', '%23'), }) }) @@ -128,7 +128,7 @@ export function TagMenu({ See{' '} - {tag} + {displayTag} {' '} posts @@ -142,21 +142,19 @@ export function TagMenu({ { e.preventDefault() control.close(() => { - // @ts-ignore :ron_swanson: "I know more than you" - navigation.navigate('SearchTab', { - screen: 'Search', - params: { - q: - tag + - (authorHandle ? ` from:${authorHandle}` : ''), - }, + navigation.push('Hashtag', { + tag: tag.replaceAll('#', '%23'), + author: authorHandle, }) }) @@ -190,7 +188,7 @@ export function TagMenu({ See{' '} - {tag} + {displayTag} {' '} posts by this user @@ -207,8 +205,8 @@ export function TagMenu({