From fe403f64ab0d0e62a2d624f2d7f67092454bb4f4 Mon Sep 17 00:00:00 2001 From: Kev Date: Sun, 22 Dec 2024 02:45:37 +0100 Subject: [PATCH] Ask before navigating away from composer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes the 'Discard draft?' post show up if you click on a link within the composer. That fixes a bug where you write a reply to a post and then lose the reply by clicking on the avatar of the person who you’re responding to. You would not be prompted whether you want to discard your draft like when clicking on **Cancel**. This bug also applies to hovering over the avatar and clicking on one of the links there. --- src/components/Link.tsx | 11 +- src/components/ProfileHoverCard/index.web.tsx | 21 +- src/components/Prompt.tsx | 14 +- src/view/com/composer/Composer.tsx | 190 ++++++++++-------- 4 files changed, 136 insertions(+), 100 deletions(-) diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 3cd593a106..d1964180e9 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, {useContext} from 'react' import {GestureResponderEvent} from 'react-native' import {sanitizeUrl} from '@braintree/sanitize-url' import {StackActions, useLinkProps} from '@react-navigation/native' @@ -17,6 +17,7 @@ import { import {isNative, isWeb} from '#/platform/detection' import {shouldClickOpenNewTab} from '#/platform/urls' import {useModalControls} from '#/state/modals' +import {ComposerContext} from '#/view/com/composer/Composer' import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf' import {Button, ButtonProps} from '#/components/Button' import {useInteractionState} from '#/components/hooks/useInteractionState' @@ -84,9 +85,10 @@ export function useLink({ const isExternal = isExternalUrl(href) const {openModal, closeModal} = useModalControls() const openLink = useOpenLink() + const composerContext = useContext(ComposerContext) const onPress = React.useCallback( - (e: GestureResponderEvent) => { + async (e: GestureResponderEvent) => { const exitEarlyIfFalse = outerOnPress?.(e) if (exitEarlyIfFalse === false) return @@ -102,6 +104,10 @@ export function useLink({ e.preventDefault() } + if (composerContext) { + await composerContext.attemptNavigation() + } + if (requiresWarning) { openModal({ name: 'link-warning', @@ -152,6 +158,7 @@ export function useLink({ closeModal, action, navigation, + composerContext, ], ) diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 3e58ced902..a9e911180b 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -14,6 +14,7 @@ import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {usePrefetchProfileQuery, useProfileQuery} from '#/state/queries/profile' import {useSession} from '#/state/session' +import {ComposerContext} from '#/view/com/composer/Composer' import {formatCount} from '#/view/com/util/numeric/format' import {UserAvatar} from '#/view/com/util/UserAvatar' import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle' @@ -306,6 +307,8 @@ export function ProfileHoverCardInner(props: ProfileHoverCardProps) { : `fadeIn ${SHOW_DURATION}ms both`, } + const composerContext = React.useContext(ComposerContext) + return ( -
-
- + +
+
+ +
-
+ )} diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index 7b33c6e256..5badcfdd51 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -27,10 +27,12 @@ export function Outer({ control, testID, nativeOptions, + onCancel, }: React.PropsWithChildren<{ control: Dialog.DialogControlProps testID?: string nativeOptions?: Omit + onCancel?: () => void }>) { const {gtMobile} = useBreakpoints() const titleId = React.useId() @@ -45,6 +47,7 @@ export function Outer({ @@ -108,18 +111,21 @@ export function Actions({children}: React.PropsWithChildren<{}>) { export function Cancel({ cta, + onCancel, }: { /** * Optional i18n string. If undefined, it will default to "Cancel". */ cta?: string + onCancel?: () => void }) { const {_} = useLingui() const {gtMobile} = useBreakpoints() const {close} = Dialog.useDialogContext() const onPress = React.useCallback(() => { + onCancel?.() close() - }, [close]) + }, [onCancel, close]) return (