diff options
Diffstat (limited to 'src/view')
21 files changed, 270 insertions, 46 deletions
diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index a38920309..5c262977f 100644 --- a/src/view/com/auth/create/Step2.tsx +++ b/src/view/com/auth/create/Step2.tsx @@ -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}} /> } diff --git a/src/view/com/auth/login/LoginForm.tsx b/src/view/com/auth/login/LoginForm.tsx index 821660b7b..3202d69c5 100644 --- a/src/view/com/auth/login/LoginForm.tsx +++ b/src/view/com/auth/login/LoginForm.tsx @@ -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`)) diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 17f9513b7..20be585c2 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -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} diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 199f1f749..c62d11201 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -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', diff --git a/src/view/com/composer/text-input/web/TagDecorator.ts b/src/view/com/composer/text-input/web/TagDecorator.ts new file mode 100644 index 000000000..d820ec3f0 --- /dev/null +++ b/src/view/com/composer/text-input/web/TagDecorator.ts @@ -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] + }, +}) diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index 60814e837..2aacdb89d 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -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 } diff --git a/src/view/com/pager/TabBar.tsx b/src/view/com/pager/TabBar.tsx index dadcfcebd..3204bb23e 100644 --- a/src/view/com/pager/TabBar.tsx +++ b/src/view/com/pager/TabBar.tsx @@ -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, + paddingHorizontal: 10, + justifyContent: 'center', + }, + itemInner: { paddingBottom: 10, - paddingHorizontal: isWeb ? 8 : 0, borderBottomWidth: 3, borderBottomColor: 'transparent', - justifyContent: 'center', }, }) diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index 434f018fc..a7ee42a94 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -437,6 +437,7 @@ function PostThreadLoaded({ // @ts-ignore our .web version only -prf desktopFixedHeight removeClippedSubviews={isAndroid ? false : undefined} + windowSize={11} /> ) } diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 392a427d6..0d50104cd 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -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} diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index aec916adb..5fa4da84e 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -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} diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 6f64de181..47a964ab1 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -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} diff --git a/src/view/com/util/List.web.tsx b/src/view/com/util/List.web.tsx index 29bad2db8..936bac198 100644 --- a/src/view/com/util/List.web.tsx +++ b/src/view/com/util/List.web.tsx @@ -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, }, diff --git a/src/view/com/util/forms/NativeDropdown.web.tsx b/src/view/com/util/forms/NativeDropdown.web.tsx index 9e9888ad8..052e7ca13 100644 --- a/src/view/com/util/forms/NativeDropdown.web.tsx +++ b/src/view/com/util/forms/NativeDropdown.web.tsx @@ -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, diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 1dfb687df..09850a7f5 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -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 && { diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index c128a6f00..35b091269 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -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={{}} />} diff --git a/src/view/com/util/text/RichText.tsx b/src/view/com/util/text/RichText.tsx index b6d461224..0ec3f3181 100644 --- a/src/view/com/util/text/RichText.tsx +++ b/src/view/com/util/text/RichText.tsx @@ -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> + ) +} diff --git a/src/view/icons/index.tsx b/src/view/icons/index.tsx index b7bbf1600..ede1e6335 100644 --- a/src/view/icons/index.tsx +++ b/src/view/icons/index.tsx @@ -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, ) diff --git a/src/view/screens/Moderation.tsx b/src/view/screens/Moderation.tsx index 9db358064..2848905c6 100644 --- a/src/view/screens/Moderation.tsx +++ b/src/view/screens/Moderation.tsx @@ -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]} diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 142726701..42eec53d3 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -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} /> )} diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 6b0cc6808..d895d8851 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -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 /> </> diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 97c065502..71dccb8c4 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -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 && ( |