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 31187112f6..3aebfbba20 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 0a654fed28..8c963909be 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 3d5f08026f..5d82d7e5ec 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 ef341154df..ba2cdb39bc 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 d52d3c0e61..e50fb7f091 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 0ec3f3181c..f4ade30e55 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 3f18069069..f9ecfba554 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 6dfc24f3b6..b96d1f835a 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 50fb9a2fd4..413d7ff691 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 78090591cc..8f2275a7f4 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 e9dd9202df..378d520bcd 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 2fec7a1880..c18c0d6a27 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 3aebfbba20..4fcb4c8122 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 7c0d4fbcaf..453b135136 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 378d520bcd..cd3b7864a6 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 f13d568b77..6b76acc948 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 0aeeeb6add..c650c1f407 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 0000000000..cf00734f04 --- /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 5d82d7e5ec..1a14415cf8 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 c18c0d6a27..c9ced9a54c 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 83498846b4..1a14415cf8 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 10718fe93b..35b0912698 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 64e067593b..b30b4491bd 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 bbd16465a7..aa3ecb7fc2 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 f51efb7b42..d7b7231c60 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 7ad9beb566..99ac8c44af 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -123,8 +123,7 @@ function HomeScreenReady({ return (