Merge branch 'main' into patch-3
This commit is contained in:
commit
8d394a3541
69 changed files with 2060 additions and 125 deletions
|
@ -133,8 +133,8 @@ function IsValidIcon({valid}: {valid: boolean}) {
|
|||
const t = useTheme()
|
||||
|
||||
if (!valid) {
|
||||
return <Check size="md" style={{color: t.palette.negative_500}} />
|
||||
return <Times size="md" style={{color: t.palette.negative_500}} />
|
||||
}
|
||||
|
||||
return <Times size="md" style={{color: t.palette.positive_700}} />
|
||||
return <Check size="md" style={{color: t.palette.positive_700}} />
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ export const LoginForm = ({
|
|||
const errMsg = e.toString()
|
||||
setIsProcessing(false)
|
||||
if (errMsg.includes('Authentication Required')) {
|
||||
logger.info('Failed to login due to invalid credentials', {
|
||||
logger.debug('Failed to login due to invalid credentials', {
|
||||
error: errMsg,
|
||||
})
|
||||
setError(_(msg`Invalid username or password`))
|
||||
|
|
|
@ -190,12 +190,11 @@ export const TextInput = forwardRef(function TextInputImpl(
|
|||
let i = 0
|
||||
|
||||
return Array.from(richtext.segments()).map(segment => {
|
||||
const isTag = AppBskyRichtextFacet.isTag(segment.facet?.features?.[0])
|
||||
return (
|
||||
<Text
|
||||
key={i++}
|
||||
style={[
|
||||
segment.facet && !isTag ? pal.link : pal.text,
|
||||
segment.facet ? pal.link : pal.text,
|
||||
styles.textInputFormatting,
|
||||
]}>
|
||||
{segment.text}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {Portal} from '#/components/Portal'
|
|||
import {Text} from '../../util/text/Text'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
||||
import {TagDecorator} from './web/TagDecorator'
|
||||
|
||||
export interface TextInputRef {
|
||||
focus: () => void
|
||||
|
@ -67,6 +68,7 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
|||
() => [
|
||||
Document,
|
||||
LinkDecorator,
|
||||
TagDecorator,
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'mention',
|
||||
|
|
83
src/view/com/composer/text-input/web/TagDecorator.ts
Normal file
83
src/view/com/composer/text-input/web/TagDecorator.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* TipTap is a stateful rich-text editor, which is extremely useful
|
||||
* when you _want_ it to be stateful formatting such as bold and italics.
|
||||
*
|
||||
* However we also use "stateless" behaviors, specifically for URLs
|
||||
* where the text itself drives the formatting.
|
||||
*
|
||||
* This plugin uses a regex to detect URIs and then applies
|
||||
* link decorations (a <span> with the "autolink") class. That avoids
|
||||
* adding any stateful formatting to TipTap's document model.
|
||||
*
|
||||
* We then run the URI detection again when constructing the
|
||||
* RichText object from TipTap's output and merge their features into
|
||||
* the facet-set.
|
||||
*/
|
||||
|
||||
import {Mark} from '@tiptap/core'
|
||||
import {Plugin, PluginKey} from '@tiptap/pm/state'
|
||||
import {Node as ProsemirrorNode} from '@tiptap/pm/model'
|
||||
import {Decoration, DecorationSet} from '@tiptap/pm/view'
|
||||
|
||||
function getDecorations(doc: ProsemirrorNode) {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
doc.descendants((node, pos) => {
|
||||
if (node.isText && node.text) {
|
||||
const regex = /(?:^|\s)(#[^\d\s]\S*)(?=\s)?/g
|
||||
const textContent = node.textContent
|
||||
|
||||
let match
|
||||
while ((match = regex.exec(textContent))) {
|
||||
const [matchedString, tag] = match
|
||||
|
||||
if (tag.length > 66) continue
|
||||
|
||||
const [trailingPunc = ''] = tag.match(/\p{P}+$/u) || []
|
||||
|
||||
const from = match.index + matchedString.indexOf(tag)
|
||||
const to = from + (tag.length - trailingPunc.length)
|
||||
|
||||
decorations.push(
|
||||
Decoration.inline(pos + from, pos + to, {
|
||||
class: 'autolink',
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return DecorationSet.create(doc, decorations)
|
||||
}
|
||||
|
||||
const tagDecoratorPlugin: Plugin = new Plugin({
|
||||
key: new PluginKey('link-decorator'),
|
||||
|
||||
state: {
|
||||
init: (_, {doc}) => getDecorations(doc),
|
||||
apply: (transaction, decorationSet) => {
|
||||
if (transaction.docChanged) {
|
||||
return getDecorations(transaction.doc)
|
||||
}
|
||||
return decorationSet.map(transaction.mapping, transaction.doc)
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
decorations(state) {
|
||||
return tagDecoratorPlugin.getState(state)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const TagDecorator = Mark.create({
|
||||
name: 'tag-decorator',
|
||||
priority: 1000,
|
||||
keepOnSplit: false,
|
||||
inclusive() {
|
||||
return true
|
||||
},
|
||||
addProseMirrorPlugins() {
|
||||
return [tagDecoratorPlugin]
|
||||
},
|
||||
})
|
|
@ -200,21 +200,12 @@ export function FeedPage({
|
|||
function useHeaderOffset() {
|
||||
const {isDesktop, isTablet} = useWebMediaQueries()
|
||||
const {fontScale} = useWindowDimensions()
|
||||
const {hasSession} = useSession()
|
||||
if (isDesktop || isTablet) {
|
||||
return 0
|
||||
}
|
||||
if (hasSession) {
|
||||
const navBarPad = 16
|
||||
const navBarText = 21 * fontScale
|
||||
const tabBarPad = 20 + 3 // nav bar padding + border
|
||||
const tabBarText = 16 * fontScale
|
||||
const magic = 7 * fontScale
|
||||
return navBarPad + navBarText + tabBarPad + tabBarText + magic
|
||||
} else {
|
||||
const navBarPad = 16
|
||||
const navBarText = 21 * fontScale
|
||||
const magic = 4 * fontScale
|
||||
return navBarPad + navBarText + magic
|
||||
}
|
||||
const navBarHeight = 42
|
||||
const tabBarPad = 10 + 10 + 3 // padding + border
|
||||
const normalLineHeight = 1.2
|
||||
const tabBarText = 16 * normalLineHeight * fontScale
|
||||
return navBarHeight + tabBarPad + tabBarText
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import {Text} from '../util/text/Text'
|
|||
import {PressableWithHover} from '../util/PressableWithHover'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {DraggableScrollView} from './DraggableScrollView'
|
||||
|
||||
export interface TabBarProps {
|
||||
|
@ -32,13 +31,15 @@ export function TabBar({
|
|||
[indicatorColor, pal],
|
||||
)
|
||||
const {isDesktop, isTablet} = useWebMediaQueries()
|
||||
const styles = isDesktop || isTablet ? desktopStyles : mobileStyles
|
||||
|
||||
// scrolls to the selected item when the page changes
|
||||
useEffect(() => {
|
||||
scrollElRef.current?.scrollTo({
|
||||
x: itemXs[selectedPage] || 0,
|
||||
x:
|
||||
(itemXs[selectedPage] || 0) - styles.contentContainer.paddingHorizontal,
|
||||
})
|
||||
}, [scrollElRef, itemXs, selectedPage])
|
||||
}, [scrollElRef, itemXs, selectedPage, styles])
|
||||
|
||||
const onPressItem = useCallback(
|
||||
(index: number) => {
|
||||
|
@ -63,8 +64,6 @@ export function TabBar({
|
|||
[],
|
||||
)
|
||||
|
||||
const styles = isDesktop || isTablet ? desktopStyles : mobileStyles
|
||||
|
||||
return (
|
||||
<View testID={testID} style={[pal.view, styles.outer]}>
|
||||
<DraggableScrollView
|
||||
|
@ -80,15 +79,17 @@ export function TabBar({
|
|||
testID={`${testID}-selector-${i}`}
|
||||
key={`${item}-${i}`}
|
||||
onLayout={e => onItemLayout(e, i)}
|
||||
style={[styles.item, selected && indicatorStyle]}
|
||||
style={styles.item}
|
||||
hoverStyle={pal.viewLight}
|
||||
onPress={() => onPressItem(i)}>
|
||||
<Text
|
||||
type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'}
|
||||
testID={testID ? `${testID}-${item}` : undefined}
|
||||
style={selected ? pal.text : pal.textLight}>
|
||||
{item}
|
||||
</Text>
|
||||
<View style={[styles.itemInner, selected && indicatorStyle]}>
|
||||
<Text
|
||||
type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'}
|
||||
testID={testID ? `${testID}-${item}` : undefined}
|
||||
style={selected ? pal.text : pal.textLight}>
|
||||
{item}
|
||||
</Text>
|
||||
</View>
|
||||
</PressableWithHover>
|
||||
)
|
||||
})}
|
||||
|
@ -103,18 +104,18 @@ const desktopStyles = StyleSheet.create({
|
|||
width: 598,
|
||||
},
|
||||
contentContainer: {
|
||||
columnGap: 8,
|
||||
marginLeft: 14,
|
||||
paddingRight: 14,
|
||||
paddingHorizontal: 0,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
item: {
|
||||
paddingTop: 14,
|
||||
paddingHorizontal: 14,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
itemInner: {
|
||||
paddingBottom: 12,
|
||||
paddingHorizontal: 10,
|
||||
borderBottomWidth: 3,
|
||||
borderBottomColor: 'transparent',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -123,17 +124,17 @@ const mobileStyles = StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
},
|
||||
contentContainer: {
|
||||
columnGap: isWeb ? 0 : 20,
|
||||
marginLeft: isWeb ? 0 : 18,
|
||||
paddingRight: isWeb ? 0 : 36,
|
||||
backgroundColor: 'transparent',
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
item: {
|
||||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
paddingHorizontal: isWeb ? 8 : 0,
|
||||
borderBottomWidth: 3,
|
||||
borderBottomColor: 'transparent',
|
||||
paddingHorizontal: 10,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
itemInner: {
|
||||
paddingBottom: 10,
|
||||
borderBottomWidth: 3,
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -437,6 +437,7 @@ function PostThreadLoaded({
|
|||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
removeClippedSubviews={isAndroid ? false : undefined}
|
||||
windowSize={11}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -327,9 +327,11 @@ let PostThreadItemLoaded = ({
|
|||
styles.postTextLargeContainer,
|
||||
]}>
|
||||
<RichText
|
||||
enableTags
|
||||
selectable
|
||||
value={richText}
|
||||
style={[a.flex_1, a.text_xl]}
|
||||
selectable
|
||||
authorHandle={post.author.handle}
|
||||
/>
|
||||
</View>
|
||||
) : undefined}
|
||||
|
@ -521,9 +523,11 @@ let PostThreadItemLoaded = ({
|
|||
{richText?.text ? (
|
||||
<View style={styles.postTextContainer}>
|
||||
<RichText
|
||||
enableTags
|
||||
value={richText}
|
||||
style={[a.flex_1, a.text_md]}
|
||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||
authorHandle={post.author.handle}
|
||||
/>
|
||||
</View>
|
||||
) : undefined}
|
||||
|
|
|
@ -184,10 +184,12 @@ function PostInner({
|
|||
{richText.text ? (
|
||||
<View style={styles.postTextContainer}>
|
||||
<RichText
|
||||
enableTags
|
||||
testID="postText"
|
||||
value={richText}
|
||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||
style={[a.flex_1, a.text_md]}
|
||||
authorHandle={post.author.handle}
|
||||
/>
|
||||
</View>
|
||||
) : undefined}
|
||||
|
|
|
@ -347,10 +347,12 @@ let PostContent = ({
|
|||
{richText.text ? (
|
||||
<View style={styles.postTextContainer}>
|
||||
<RichText
|
||||
enableTags
|
||||
testID="postText"
|
||||
value={richText}
|
||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||
style={[a.flex_1, a.text_md]}
|
||||
authorHandle={postAuthor.handle}
|
||||
/>
|
||||
</View>
|
||||
) : undefined}
|
||||
|
|
|
@ -172,7 +172,7 @@ function ListImpl<ItemT>(
|
|||
<View
|
||||
ref={containerRef}
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
!isMobile && styles.sideBorders,
|
||||
contentContainerStyle,
|
||||
desktopFixedHeight ? styles.minHeightViewport : null,
|
||||
pal.border,
|
||||
|
@ -304,7 +304,7 @@ export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
|
|||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
sideBorders: {
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@ export const DropdownMenuItem = (props: ItemProps & {testID?: string}) => {
|
|||
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
className="nativeDropdown-item"
|
||||
{...props}
|
||||
style={StyleSheet.flatten([
|
||||
styles.item,
|
||||
|
@ -232,6 +233,10 @@ const styles = StyleSheet.create({
|
|||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
borderRadius: 8,
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
||||
outline: 0,
|
||||
border: 0,
|
||||
},
|
||||
itemTitle: {
|
||||
fontSize: 16,
|
||||
|
|
|
@ -34,6 +34,7 @@ import {useLingui} from '@lingui/react'
|
|||
import {useSession} from '#/state/session'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||
|
||||
let PostDropdownBtn = ({
|
||||
testID,
|
||||
|
@ -67,6 +68,7 @@ let PostDropdownBtn = ({
|
|||
const {hidePost} = useHiddenPostsApi()
|
||||
const openLink = useOpenLink()
|
||||
const navigation = useNavigation()
|
||||
const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
|
||||
|
||||
const rootUri = record.reply?.root?.uri || postUri
|
||||
const isThreadMuted = mutedThreads.includes(rootUri)
|
||||
|
@ -210,6 +212,20 @@ let PostDropdownBtn = ({
|
|||
web: 'comment-slash',
|
||||
},
|
||||
},
|
||||
hasSession && {
|
||||
label: _(msg`Mute words & tags`),
|
||||
onPress() {
|
||||
mutedWordsDialogControl.open()
|
||||
},
|
||||
testID: 'postDropdownMuteWordsBtn',
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'speaker.slash',
|
||||
},
|
||||
android: 'ic_lock_silent_mode',
|
||||
web: 'filter',
|
||||
},
|
||||
},
|
||||
hasSession &&
|
||||
!isAuthor &&
|
||||
!isPostHidden && {
|
||||
|
|
|
@ -128,10 +128,12 @@ export function QuoteEmbed({
|
|||
) : null}
|
||||
{richText ? (
|
||||
<RichText
|
||||
enableTags
|
||||
value={richText}
|
||||
style={[a.text_md]}
|
||||
numberOfLines={20}
|
||||
disableLinks
|
||||
authorHandle={quote.author.handle}
|
||||
/>
|
||||
) : null}
|
||||
{embed && <PostEmbeds embed={embed} moderation={{}} />}
|
||||
|
|
|
@ -7,6 +7,9 @@ import {lh} from 'lib/styles'
|
|||
import {toShortUrl} from 'lib/strings/url-helpers'
|
||||
import {useTheme, TypographyVariant} from 'lib/ThemeContext'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {makeTagLink} from 'lib/routes/links'
|
||||
import {TagMenu, useTagMenuControl} from '#/components/TagMenu'
|
||||
import {isNative} from '#/platform/detection'
|
||||
|
||||
const WORD_WRAP = {wordWrap: 1}
|
||||
|
||||
|
@ -82,6 +85,7 @@ export function RichText({
|
|||
for (const segment of richText.segments()) {
|
||||
const link = segment.link
|
||||
const mention = segment.mention
|
||||
const tag = segment.tag
|
||||
if (
|
||||
!noLinks &&
|
||||
mention &&
|
||||
|
@ -115,6 +119,21 @@ export function RichText({
|
|||
/>,
|
||||
)
|
||||
}
|
||||
} else if (
|
||||
!noLinks &&
|
||||
tag &&
|
||||
AppBskyRichtextFacet.validateTag(tag).success
|
||||
) {
|
||||
els.push(
|
||||
<RichTextTag
|
||||
key={key}
|
||||
text={segment.text}
|
||||
type={type}
|
||||
style={style}
|
||||
lineHeightStyle={lineHeightStyle}
|
||||
selectable={selectable}
|
||||
/>,
|
||||
)
|
||||
} else {
|
||||
els.push(segment.text)
|
||||
}
|
||||
|
@ -133,3 +152,50 @@ export function RichText({
|
|||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
function RichTextTag({
|
||||
text: tag,
|
||||
type,
|
||||
style,
|
||||
lineHeightStyle,
|
||||
selectable,
|
||||
}: {
|
||||
text: string
|
||||
type?: TypographyVariant
|
||||
style?: StyleProp<TextStyle>
|
||||
lineHeightStyle?: TextStyle
|
||||
selectable?: boolean
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const control = useTagMenuControl()
|
||||
|
||||
const open = React.useCallback(() => {
|
||||
control.open()
|
||||
}, [control])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TagMenu control={control} tag={tag}>
|
||||
{isNative ? (
|
||||
<TextLink
|
||||
type={type}
|
||||
text={tag}
|
||||
// segment.text has the leading "#" while tag.tag does not
|
||||
href={makeTagLink(tag)}
|
||||
style={[style, lineHeightStyle, pal.link, {pointerEvents: 'auto'}]}
|
||||
dataSet={WORD_WRAP}
|
||||
selectable={selectable}
|
||||
onPress={open}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
selectable={selectable}
|
||||
type={type}
|
||||
style={[style, lineHeightStyle, pal.link, {pointerEvents: 'auto'}]}>
|
||||
{tag}
|
||||
</Text>
|
||||
)}
|
||||
</TagMenu>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ import {faUsersSlash} from '@fortawesome/free-solid-svg-icons/faUsersSlash'
|
|||
import {faX} from '@fortawesome/free-solid-svg-icons/faX'
|
||||
import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark'
|
||||
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'
|
||||
import {faFilter} from '@fortawesome/free-solid-svg-icons/faFilter'
|
||||
|
||||
library.add(
|
||||
faAddressCard,
|
||||
|
@ -208,4 +209,5 @@ library.add(
|
|||
faX,
|
||||
faXmark,
|
||||
faChevronDown,
|
||||
faFilter,
|
||||
)
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
useProfileUpdateMutation,
|
||||
} from '#/state/queries/profile'
|
||||
import {ScrollView} from '../com/util/Views'
|
||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>
|
||||
export function ModerationScreen({}: Props) {
|
||||
|
@ -40,6 +41,7 @@ export function ModerationScreen({}: Props) {
|
|||
const {screen, track} = useAnalytics()
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const {openModal} = useModalControls()
|
||||
const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
|
@ -71,7 +73,7 @@ export function ModerationScreen({}: Props) {
|
|||
accessibilityRole="tab"
|
||||
accessibilityLabel={_(msg`Content filtering`)}
|
||||
accessibilityHint={_(
|
||||
msg`Opens modal for content filtering preferences`,
|
||||
msg`Opens modal for content filtering settings`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
|
@ -83,6 +85,23 @@ export function ModerationScreen({}: Props) {
|
|||
<Trans>Content filtering</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
testID="mutedWordsBtn"
|
||||
style={[styles.linkCard, pal.view]}
|
||||
onPress={() => mutedWordsDialogControl.open()}
|
||||
accessibilityRole="tab"
|
||||
accessibilityLabel={_(msg`Muted words & tags`)}
|
||||
accessibilityHint={_(msg`Open modal for muted words settings`)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
icon="filter"
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
</View>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>Muted words & tags</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Link
|
||||
testID="moderationlistsBtn"
|
||||
style={[styles.linkCard, pal.view]}
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
|
@ -53,6 +53,7 @@ import {listenSoftReset} from '#/state/events'
|
|||
import {s} from '#/lib/styles'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import {augmentSearchQuery} from '#/lib/strings/helpers'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
|
||||
function Loader() {
|
||||
const pal = usePalette('default')
|
||||
|
@ -448,6 +449,7 @@ export function SearchScreenInner({
|
|||
export function SearchScreen(
|
||||
props: NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>,
|
||||
) {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const theme = useTheme()
|
||||
const textInput = React.useRef<TextInput>(null)
|
||||
const {_} = useLingui()
|
||||
|
@ -472,6 +474,27 @@ export function SearchScreen(
|
|||
React.useState(false)
|
||||
const [searchHistory, setSearchHistory] = React.useState<string[]>([])
|
||||
|
||||
/**
|
||||
* The Search screen's `q` param
|
||||
*/
|
||||
const queryParam = props.route?.params?.q
|
||||
|
||||
/**
|
||||
* If `true`, this means we received new instructions from the router. This
|
||||
* is handled in a effect, and used to update the value of `query` locally
|
||||
* within this screen.
|
||||
*/
|
||||
const routeParamsMismatch = queryParam && queryParam !== query
|
||||
|
||||
React.useEffect(() => {
|
||||
if (queryParam && routeParamsMismatch) {
|
||||
// reset immediately and let local state take over
|
||||
navigation.setParams({q: ''})
|
||||
// update query for next search
|
||||
setQuery(queryParam)
|
||||
}
|
||||
}, [queryParam, routeParamsMismatch, navigation])
|
||||
|
||||
React.useEffect(() => {
|
||||
const loadSearchHistory = async () => {
|
||||
try {
|
||||
|
@ -774,6 +797,8 @@ export function SearchScreen(
|
|||
)}
|
||||
</View>
|
||||
</CenteredView>
|
||||
) : routeParamsMismatch ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<SearchScreenInner query={query} />
|
||||
)}
|
||||
|
|
|
@ -29,6 +29,7 @@ import {useSession} from '#/state/session'
|
|||
import {useCloseAnyActiveElement} from '#/state/util'
|
||||
import * as notifications from 'lib/notifications/notifications'
|
||||
import {Outlet as PortalOutlet} from '#/components/Portal'
|
||||
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
|
||||
|
||||
function ShellInner() {
|
||||
const isDrawerOpen = useIsDrawerOpen()
|
||||
|
@ -94,6 +95,7 @@ function ShellInner() {
|
|||
</View>
|
||||
<Composer winHeight={winDim.height} />
|
||||
<ModalsContainer />
|
||||
<MutedWordsDialog />
|
||||
<PortalOutlet />
|
||||
<Lightbox />
|
||||
</>
|
||||
|
|
|
@ -16,6 +16,7 @@ import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell'
|
|||
import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import {Outlet as PortalOutlet} from '#/components/Portal'
|
||||
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
|
||||
|
||||
function ShellInner() {
|
||||
const isDrawerOpen = useIsDrawerOpen()
|
||||
|
@ -40,6 +41,7 @@ function ShellInner() {
|
|||
</ErrorBoundary>
|
||||
<Composer winHeight={0} />
|
||||
<ModalsContainer />
|
||||
<MutedWordsDialog />
|
||||
<PortalOutlet />
|
||||
<Lightbox />
|
||||
{!isDesktop && isDrawerOpen && (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue