From 1a5afccdb809d7a8a2e9ebb356b499135a5ff175 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 29 Feb 2024 10:31:45 -0600 Subject: [PATCH 01/20] Add TagMenu controls stub on web (#3028) --- src/components/TagMenu/index.web.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/TagMenu/index.web.tsx b/src/components/TagMenu/index.web.tsx index 31187112..3aebfbba 100644 --- a/src/components/TagMenu/index.web.tsx +++ b/src/components/TagMenu/index.web.tsx @@ -14,8 +14,21 @@ import { } from '#/state/queries/preferences' import {enforceLen} from '#/lib/strings/helpers' import {web} from '#/alf' +import * as Dialog from '#/components/Dialog' -export function useTagMenuControl() {} +export function useTagMenuControl(): Dialog.DialogControlProps { + return { + id: '', + // @ts-ignore + ref: null, + open: () => { + throw new Error(`TagMenu controls are only available on native platforms`) + }, + close: () => { + throw new Error(`TagMenu controls are only available on native platforms`) + }, + } +} export function TagMenu({ children, From 39d324ab8bd99ca0e19f3e8f4dea5a61d54e6bb4 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 29 Feb 2024 15:23:28 -0800 Subject: [PATCH 02/20] 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/Link.tsx | 14 +++++++------- src/components/RichText.tsx | 3 +-- src/lib/strings/url-helpers.ts | 12 +++--------- src/view/com/util/Link.tsx | 10 +++++----- src/view/com/util/text/RichText.tsx | 1 - src/view/screens/Storybook/Links.tsx | 28 ++++++++-------------------- 6 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 0a654fed..8c963909 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -49,7 +49,7 @@ type BaseLinkProps = Pick< * * Note: atm this only works for `InlineLink`s with a string child. */ - warnOnMismatchingTextChild?: boolean + disableMismatchWarning?: boolean /** * Callback for when the link is pressed. Prevent default and return `false` @@ -69,7 +69,7 @@ export function useLink({ to, displayText, action = 'push', - warnOnMismatchingTextChild, + disableMismatchWarning, onPress: outerOnPress, }: BaseLinkProps & { displayText: string @@ -90,7 +90,7 @@ export function useLink({ if (exitEarlyIfFalse === false) return const requiresWarning = Boolean( - warnOnMismatchingTextChild && + !disableMismatchWarning && displayText && isExternal && linkRequiresWarning(href, displayText), @@ -148,7 +148,7 @@ export function useLink({ }, [ outerOnPress, - warnOnMismatchingTextChild, + disableMismatchWarning, displayText, isExternal, href, @@ -167,7 +167,7 @@ export function useLink({ } } -export type LinkProps = Omit & +export type LinkProps = Omit & Omit /** @@ -226,7 +226,7 @@ export function InlineLink({ children, to, action = 'push', - warnOnMismatchingTextChild, + disableMismatchWarning, style, onPress: outerOnPress, download, @@ -239,7 +239,7 @@ export function InlineLink({ to, displayText: stringChildren ? children : '', action, - warnOnMismatchingTextChild, + disableMismatchWarning, onPress: outerOnPress, }) const { diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 3d5f0802..5d82d7e5 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)} , ) diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index ef341154..ba2cdb39 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -157,17 +157,11 @@ export function linkRequiresWarning(uri: string, label: string) { const host = urip.hostname.toLowerCase() - if (host === 'bsky.app') { + // Hosts that end with bsky.app or bsky.social should be trusted by default. + if (host.endsWith('bsky.app') || host.endsWith('bsky.social')) { // if this is a link to internal content, // warn if it represents itself as a URL to another app - if ( - labelDomain && - labelDomain !== 'bsky.app' && - isPossiblyAUrl(labelDomain) - ) { - return true - } - return false + return !!labelDomain && labelDomain !== host && isPossiblyAUrl(labelDomain) } else { // if this is a link to external content, // warn if the label doesnt match the target diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index d52d3c0e..e50fb7f0 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -159,7 +159,7 @@ export const TextLink = memo(function TextLink({ dataSet, title, onPress, - warnOnMismatchingLabel, + disableMismatchWarning, navigationAction, ...orgProps }: { @@ -172,7 +172,7 @@ export const TextLink = memo(function TextLink({ lineHeight?: number dataSet?: any title?: string - warnOnMismatchingLabel?: boolean + disableMismatchWarning?: boolean navigationAction?: 'push' | 'replace' | 'navigate' } & TextProps) { const {...props} = useLinkProps({to: sanitizeUrl(href)}) @@ -180,14 +180,14 @@ export const TextLink = memo(function TextLink({ const {openModal, closeModal} = useModalControls() const openLink = useOpenLink() - if (warnOnMismatchingLabel && typeof text !== 'string') { + if (!disableMismatchWarning && typeof text !== 'string') { console.error('Unable to detect mismatching label') } props.onPress = React.useCallback( (e?: Event) => { const requiresWarning = - warnOnMismatchingLabel && + !disableMismatchWarning && linkRequiresWarning(href, typeof text === 'string' ? text : '') if (requiresWarning) { e?.preventDefault?.() @@ -227,7 +227,7 @@ export const TextLink = memo(function TextLink({ navigation, href, text, - warnOnMismatchingLabel, + disableMismatchWarning, navigationAction, openLink, ], diff --git a/src/view/com/util/text/RichText.tsx b/src/view/com/util/text/RichText.tsx index 0ec3f318..f4ade30e 100644 --- a/src/view/com/util/text/RichText.tsx +++ b/src/view/com/util/text/RichText.tsx @@ -114,7 +114,6 @@ export function RichText({ href={link.uri} style={[style, lineHeightStyle, pal.link, {pointerEvents: 'auto'}]} dataSet={WORD_WRAP} - warnOnMismatchingLabel selectable={selectable} />, ) diff --git a/src/view/screens/Storybook/Links.tsx b/src/view/screens/Storybook/Links.tsx index 3f180690..f9ecfba5 100644 --- a/src/view/screens/Storybook/Links.tsx +++ b/src/view/screens/Storybook/Links.tsx @@ -4,7 +4,7 @@ import {View} from 'react-native' import {useTheme, atoms as a} from '#/alf' import {ButtonText} from '#/components/Button' import {InlineLink, Link} from '#/components/Link' -import {H1, H3, Text} from '#/components/Typography' +import {H1, Text} from '#/components/Typography' export function Links() { const t = useTheme() @@ -13,31 +13,19 @@ export function Links() {

Links

- - External + + https://google.com - -

External with custom children

+ + External with custom children (google.com) - External with custom children + Internal (bsky.social) - - https://bsky.social - - - Internal + + Internal (bsky.app) Date: Thu, 29 Feb 2024 15:27:00 -0800 Subject: [PATCH 03/20] Dismiss keyboard when closing dialog (#3053) --- src/components/Dialog/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 6dfc24f3..b96d1f83 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -1,5 +1,5 @@ import React, {useImperativeHandle} from 'react' -import {View, Dimensions} from 'react-native' +import {View, Dimensions, Keyboard} from 'react-native' import BottomSheet, { BottomSheetBackdrop, BottomSheetScrollView, @@ -78,6 +78,7 @@ export function Outer({ const onChange = React.useCallback( (index: number) => { if (index === -1) { + Keyboard.dismiss() try { closeCallback.current?.() } catch (e: any) { @@ -190,8 +191,15 @@ export function ScrollableInner({children, style}: DialogInnerProps) { export function Handle() { const t = useTheme() + + const onTouchStart = React.useCallback(() => { + Keyboard.dismiss() + }, []) + return ( - + Date: Fri, 1 Mar 2024 06:30:17 +0700 Subject: [PATCH 04/20] Reset button/input font set by UA (#3038) * fix: reset button/input fonts set by UA * fix: inherit line-height as well --- bskyweb/templates/base.html | 6 ++++++ web/index.html | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html index 50fb9a2f..413d7ff6 100644 --- a/bskyweb/templates/base.html +++ b/bskyweb/templates/base.html @@ -44,6 +44,12 @@ scrollbar-gutter: stable both-edges; } + /* Buttons and inputs have a font set by UA, so we'll have to reset that */ + button, input, textarea { + font: inherit; + line-height: inherit; + } + /* Color theming */ /* Default will always be white */ :root { diff --git a/web/index.html b/web/index.html index 78090591..8f2275a7 100644 --- a/web/index.html +++ b/web/index.html @@ -48,6 +48,12 @@ scrollbar-gutter: stable both-edges; } + /* Buttons and inputs have a font set by UA, so we'll have to reset that */ + button, input, textarea { + font: inherit; + line-height: inherit; + } + /* Color theming */ /* Default will always be white */ :root { From cecb6e4e69eaccedf56f503589a59044fd7c6d19 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 29 Feb 2024 19:30:30 -0600 Subject: [PATCH 05/20] Bump API SDK, add validation to MutedWords (#3055) * Bump API SDK, add validation to MutedWords * Tweaks to error state * Comment * Early return --- package.json | 2 +- src/components/TagMenu/index.tsx | 6 +-- src/components/TagMenu/index.web.tsx | 5 +-- src/components/dialogs/MutedWords.tsx | 60 +++++++++++++++++++++++---- yarn.lock | 40 ++++++++++++++---- 5 files changed, 89 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index e9dd9202..378d520b 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "update-extensions": "scripts/updateExtensions.sh" }, "dependencies": { - "@atproto/api": "^0.10.0", + "@atproto/api": "^0.10.3", "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", "@emoji-mart/react": "^1.1.1", diff --git a/src/components/TagMenu/index.tsx b/src/components/TagMenu/index.tsx index 2fec7a18..c18c0d6a 100644 --- a/src/components/TagMenu/index.tsx +++ b/src/components/TagMenu/index.tsx @@ -215,14 +215,12 @@ export function TagMenu({ if (isMuted) { resetUpsert() removeMutedWord({ - value: sanitizedTag, + value: tag, targets: ['tag'], }) } else { resetRemove() - upsertMutedWord([ - {value: sanitizedTag, targets: ['tag']}, - ]) + upsertMutedWord([{value: tag, targets: ['tag']}]) } }) }}> diff --git a/src/components/TagMenu/index.web.tsx b/src/components/TagMenu/index.web.tsx index 3aebfbba..4fcb4c81 100644 --- a/src/components/TagMenu/index.web.tsx +++ b/src/components/TagMenu/index.web.tsx @@ -104,9 +104,9 @@ export function TagMenu({ : _(msg`Mute ${truncatedTag}`), onPress() { if (isMuted) { - removeMutedWord({value: sanitizedTag, targets: ['tag']}) + removeMutedWord({value: tag, targets: ['tag']}) } else { - upsertMutedWord([{value: sanitizedTag, targets: ['tag']}]) + upsertMutedWord([{value: tag, targets: ['tag']}]) } }, testID: 'tagMenuMute', @@ -127,7 +127,6 @@ export function TagMenu({ preferences, tag, truncatedTag, - sanitizedTag, upsertMutedWord, removeMutedWord, ]) diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index 7c0d4fbc..453b1351 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -2,7 +2,7 @@ import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {AppBskyActorDefs} from '@atproto/api' +import {AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' import { usePreferencesQuery, @@ -10,7 +10,14 @@ import { useRemoveMutedWordMutation, } from '#/state/queries/preferences' import {isNative} from '#/platform/detection' -import {atoms as a, useTheme, useBreakpoints, ViewStyleProp, web} from '#/alf' +import { + atoms as a, + useTheme, + useBreakpoints, + ViewStyleProp, + web, + native, +} from '#/alf' import {Text} from '#/components/Typography' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' @@ -48,24 +55,29 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) { const {isPending, mutateAsync: addMutedWord} = useUpsertMutedWordsMutation() const [field, setField] = React.useState('') const [options, setOptions] = React.useState(['content']) - const [_error, setError] = React.useState('') + const [error, setError] = React.useState('') const submit = React.useCallback(async () => { - const value = field.trim() + const sanitizedValue = sanitizeMutedWordValue(field) const targets = ['tag', options.includes('content') && 'content'].filter( Boolean, ) as AppBskyActorDefs.MutedWord['targets'] - if (!value || !targets.length) return + if (!sanitizedValue || !targets.length) { + setField('') + setError(_(msg`Please enter a valid word, tag, or phrase to mute`)) + return + } try { - await addMutedWord([{value, targets}]) + // send raw value and rely on SDK as sanitization source of truth + await addMutedWord([{value: field, targets}]) setField('') } catch (e: any) { logger.error(`Failed to save muted word`, {message: e.message}) setError(e.message) } - }, [field, options, addMutedWord, setField]) + }, [_, field, options, addMutedWord, setField]) return ( @@ -87,7 +99,12 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) { label={_(msg`Enter a word or tag`)} placeholder={_(msg`Enter a word or tag`)} value={field} - onChangeText={setField} + onChangeText={value => { + if (error) { + setError('') + } + setField(value) + }} onSubmitEditing={submit} /> @@ -99,7 +116,7 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) { + {error && ( + + + {error} + + + )} + Date: Thu, 29 Feb 2024 17:56:08 -0800 Subject: [PATCH 06/20] Bump version to 1.71 (#3057) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 378d520b..cd3b7864 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky.app", - "version": "1.70.0", + "version": "1.71.0", "private": true, "engines": { "node": ">=18" From cf8b03801fd8e98bcd2da4d099a9dfbf5876de7d Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 29 Feb 2024 17:56:29 -0800 Subject: [PATCH 07/20] 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 diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index f13d568b..6b76acc9 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 0aeeeb6a..c650c1f4 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 00000000..cf00734f --- /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 5d82d7e5..1a14415c 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 c18c0d6a..c9ced9a5 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({ - - - - {error && ( - - - {error} - - - )} - + + style={[a.text_md, a.font_bold, a.pb_sm, t.atoms.text_contrast_high]}> + Add muted words and tags + + - We recommend avoiding common words that appear in many posts, since - it can result in no posts being shown. + Posts can be muted based on their text, their tags, or both. + + + { + if (error) { + setError('') + } + setField(value) + }} + onSubmitEditing={submit} + /> + + + + + + + + + Mute in text & tags + + + + + + + + + + + + Mute in tags only + + + + + + + + + + + {error && ( + + + {error} + + + )} + + + + We recommend avoiding common words that appear in many posts, + since it can result in no posts being shown. + + + + + + + + + Your muted words + + + {isPreferencesLoading ? ( + + ) : preferencesError || !preferences ? ( + + + + We're sorry, but we weren't able to load your muted words at + this time. Please try again. + + + + ) : preferences.mutedWords.length ? ( + [...preferences.mutedWords] + .reverse() + .map((word, i) => ( + + )) + ) : ( + + + You haven't muted any words or tags yet + + + )} + + + {isNative && } + + - - - - - - Your muted words - - - {isPreferencesLoading ? ( - - ) : preferencesError || !preferences ? ( - - - - We're sorry, but we weren't able to load your muted words at - this time. Please try again. - - - - ) : preferences.mutedWords.length ? ( - [...preferences.mutedWords] - .reverse() - .map((word, i) => ( - - )) - ) : ( - - - You haven't muted any words or tags yet - - - )} - - - {isNative && } - - ) } From b07846f2fa8e2830e7871c72afd5a81d2eecfe99 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 1 Mar 2024 17:15:45 -0800 Subject: [PATCH 18/20] Revert "Enable tags inside of quotes (#3041)" (#3075) This reverts commit f016cdbca9660d9e10faefae5c34c8574795419e. --- src/components/RichText.tsx | 42 +++++++++++--------- src/view/com/util/post-embeds/QuoteEmbed.tsx | 5 +-- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 83498846..1a14415c 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -78,31 +78,40 @@ export function RichText({ const link = segment.link const mention = segment.mention const tag = segment.tag - if (mention && AppBskyRichtextFacet.validateMention(mention).success) { + if ( + mention && + AppBskyRichtextFacet.validateMention(mention).success && + !disableLinks + ) { els.push( {segment.text} , ) } else if (link && AppBskyRichtextFacet.validateLink(link).success) { - els.push( - - {toShortUrl(segment.text)} - , - ) + if (disableLinks) { + els.push(toShortUrl(segment.text)) + } else { + els.push( + + {toShortUrl(segment.text)} + , + ) + } } else if ( + !disableLinks && enableTags && tag && AppBskyRichtextFacet.validateTag(tag).success @@ -115,7 +124,6 @@ export function RichText({ style={styles} selectable={selectable} authorHandle={authorHandle} - disableLinks={disableLinks} />, ) } else { @@ -128,7 +136,7 @@ export function RichText({ @@ -143,13 +151,11 @@ function RichTextTag({ style, selectable, authorHandle, - disableLinks, }: { text: string tag: string selectable?: boolean authorHandle?: string - disableLinks?: boolean } & TextStyleProp) { const t = useTheme() const {_} = useLingui() @@ -198,7 +204,7 @@ function RichTextTag({ style={[ style, { - pointerEvents: disableLinks ? 'none' : 'auto', + pointerEvents: 'auto', color: t.palette.primary_500, }, web({ diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index 10718fe9..35b09126 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -91,10 +91,7 @@ export function QuoteEmbed({ const richText = React.useMemo( () => quote.text.trim() - ? new RichTextAPI({ - text: quote.text, - facets: quote.facets, - }) + ? new RichTextAPI({text: quote.text, facets: quote.facets}) : undefined, [quote.text, quote.facets], ) From f2249614bee2dcc82ec2b4ecdd853dc3444e626c Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 2 Mar 2024 01:34:43 +0000 Subject: [PATCH 19/20] Fix Profile tab switch jumps on Chrome (#3076) --- src/view/screens/Profile.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 64e06759..b30b4491 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -491,6 +491,8 @@ const styles = StyleSheet.create({ container: { flexDirection: 'column', height: '100%', + // @ts-ignore Web-only. + overflowAnchor: 'none', // Fixes jumps when switching tabs while scrolled down. }, loading: { paddingVertical: 10, From b70c404d4b369d6fab0dfbafd6b31390ffd20014 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 2 Mar 2024 02:40:47 +0000 Subject: [PATCH 20/20] Sticky desktop header (#3077) --- src/view/com/home/HomeHeader.tsx | 2 +- src/view/com/home/HomeHeaderLayout.web.tsx | 76 ++++++++++---------- src/view/com/home/HomeHeaderLayoutMobile.tsx | 1 + src/view/screens/Home.tsx | 3 +- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/view/com/home/HomeHeader.tsx b/src/view/com/home/HomeHeader.tsx index bbd16465..aa3ecb7f 100644 --- a/src/view/com/home/HomeHeader.tsx +++ b/src/view/com/home/HomeHeader.tsx @@ -52,7 +52,7 @@ export function HomeHeader( ) return ( - + {children} + return } else { - return {children} + return } } -function HomeHeaderLayoutTablet({children}: {children: React.ReactNode}) { +function HomeHeaderLayoutDesktopAndTablet({ + children, + tabBarAnchor, +}: { + children: React.ReactNode + tabBarAnchor: JSX.Element | null | undefined +}) { const pal = usePalette('default') - const {headerMinimalShellTransform} = useMinimalShellMode() - const {headerHeight} = useShellLayout() const {_} = useLingui() return ( - // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf - { - headerHeight.value = e.nativeEvent.layout.height - }}> - - + + - } - /> + accessibilityHint=""> + + - {children} - + {tabBarAnchor} + + {children} + + ) } const styles = StyleSheet.create({ + bar: { + // @ts-ignore Web only + left: 'calc(50% - 300px)', + width: 600, + borderLeftWidth: 1, + borderRightWidth: 1, + }, topBar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 18, - paddingVertical: 8, - marginTop: 8, - width: '100%', + paddingTop: 16, + paddingBottom: 8, }, tabBar: { // @ts-ignore Web only position: 'sticky', - zIndex: 1, - // @ts-ignore Web only -prf - left: 'calc(50% - 300px)', - width: 600, top: 0, flexDirection: 'column', alignItems: 'center', borderLeftWidth: 1, borderRightWidth: 1, + zIndex: 1, }, }) diff --git a/src/view/com/home/HomeHeaderLayoutMobile.tsx b/src/view/com/home/HomeHeaderLayoutMobile.tsx index f51efb7b..d7b7231c 100644 --- a/src/view/com/home/HomeHeaderLayoutMobile.tsx +++ b/src/view/com/home/HomeHeaderLayoutMobile.tsx @@ -23,6 +23,7 @@ export function HomeHeaderLayoutMobile({ children, }: { children: React.ReactNode + tabBarAnchor: JSX.Element | null | undefined }) { const pal = usePalette('default') const {_} = useLingui() diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 7ad9beb5..99ac8c44 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -123,8 +123,7 @@ function HomeScreenReady({ return (