diff --git a/native/chat/inner-text-message.react.js b/native/chat/inner-text-message.react.js
--- a/native/chat/inner-text-message.react.js
+++ b/native/chat/inner-text-message.react.js
@@ -5,6 +5,7 @@
import { View, StyleSheet, TouchableWithoutFeedback } from 'react-native';
import Animated from 'react-native-reanimated';
+import { messageKey } from 'lib/shared/message-utils';
import { colorIsDark } from 'lib/shared/thread-utils';
import GestureTouchableOpacity from '../components/gesture-touchable-opacity.react';
@@ -13,6 +14,7 @@
import { useColors, colors } from '../themes/colors';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types';
import { useComposedMessageMaxWidth } from './composed-message-width';
+import { MessageContext } from './message-context.react';
import { MessageListContext } from './message-list-types';
import {
allCorners,
@@ -101,6 +103,16 @@
return [styles.text, textStyle];
}, [darkColor]);
+ // We use a MessageContext to allow MarkdownLink and MarkdownSpoiler
+ // to access the messageKey so it is 'self-aware'
+ const key = messageKey(item.messageInfo);
+ const messageContextValue = React.useMemo(
+ () => ({
+ messageKey: key,
+ }),
+ [key],
+ );
+
const message = (
@@ -111,9 +123,11 @@
style={[styles.message, cornerStyle]}
animatedStyle={messageStyle}
>
-
- {text}
-
+
+
+ {text}
+
+
diff --git a/native/chat/message-context.react.js b/native/chat/message-context.react.js
new file mode 100644
--- /dev/null
+++ b/native/chat/message-context.react.js
@@ -0,0 +1,15 @@
+// @flow
+
+import * as React from 'react';
+
+export type MessageContextType = {
+ +messageKey: string,
+};
+
+const MessageContext: React.Context = React.createContext(
+ {
+ messageKey: '',
+ },
+);
+
+export { MessageContext };
diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js
--- a/native/chat/text-message.react.js
+++ b/native/chat/text-message.react.js
@@ -46,7 +46,7 @@
// ChatContext
+chatContext: ?ChatContextType,
// MarkdownContext
- +linkModalActive: boolean,
+ +isLinkModalActive: boolean,
+linkIsBlockingPresses: boolean,
};
class TextMessage extends React.PureComponent {
@@ -62,7 +62,7 @@
verticalBounds,
overlayContext,
chatContext,
- linkModalActive,
+ isLinkModalActive,
linkIsBlockingPresses,
canCreateSidebarFromMessage,
...viewProps
@@ -71,7 +71,7 @@
let swipeOptions = 'none';
const canReply = this.canReply();
const canNavigateToSidebar = this.canNavigateToSidebar();
- if (linkModalActive) {
+ if (isLinkModalActive) {
swipeOptions = 'none';
} else if (canReply && canNavigateToSidebar) {
swipeOptions = 'both';
@@ -147,6 +147,7 @@
message,
props: { verticalBounds, linkIsBlockingPresses },
} = this;
+
if (!message || !verticalBounds || linkIsBlockingPresses) {
return;
}
@@ -208,33 +209,41 @@
function ConnectedTextMessage(props: BaseProps) {
const overlayContext = React.useContext(OverlayContext);
const chatContext = React.useContext(ChatContext);
+ const markdownContext = React.useContext(MarkdownContext);
+ invariant(markdownContext, 'markdownContext should be set');
+
+ const {
+ linkModalActive,
+ linkPressActive,
+ clearMarkdownContextData,
+ } = markdownContext;
+
+ const key = messageKey(props.item.messageInfo);
+
+ // We check if there is an ID in the respective objects - if not, we
+ // default to false. The likely situation where the former statement
+ // evaluates to null is when the thread is opened for the first time.
+ const linkIsBlockingPresses =
+ (linkModalActive[key] || linkPressActive[key]) ?? false;
+
+ const isLinkModalActive = linkModalActive[key] ?? false;
- const [linkModalActive, setLinkModalActive] = React.useState(false);
- const [linkPressActive, setLinkPressActive] = React.useState(false);
- const markdownContext = React.useMemo(
- () => ({
- setLinkModalActive,
- setLinkPressActive,
- }),
- [setLinkModalActive, setLinkPressActive],
- );
const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage(
props.item.threadInfo,
props.item.messageInfo,
);
- const linkIsBlockingPresses = linkModalActive || linkPressActive;
+ React.useEffect(() => clearMarkdownContextData, [clearMarkdownContextData]);
+
return (
-
-
-
+
);
},
);
diff --git a/native/markdown/markdown-context-provider.react.js b/native/markdown/markdown-context-provider.react.js
new file mode 100644
--- /dev/null
+++ b/native/markdown/markdown-context-provider.react.js
@@ -0,0 +1,48 @@
+// @flow
+
+import * as React from 'react';
+
+import { MarkdownContext } from './markdown-context.js';
+
+type Props = {
+ +children: React.Node,
+};
+
+function MarkdownContextProvider(props: Props): React.Node {
+ const [linkModalActive, setLinkModalActive] = React.useState<{
+ [key: string]: boolean,
+ }>({});
+ const [linkPressActive, setLinkPressActive] = React.useState<{
+ [key: string]: boolean,
+ }>({});
+
+ const clearMarkdownContextData = React.useCallback(() => {
+ setLinkModalActive({});
+ setLinkPressActive({});
+ }, []);
+
+ const contextValue = React.useMemo(
+ () => ({
+ setLinkModalActive,
+ linkModalActive,
+ setLinkPressActive,
+ linkPressActive,
+ clearMarkdownContextData,
+ }),
+ [
+ setLinkModalActive,
+ linkModalActive,
+ setLinkPressActive,
+ linkPressActive,
+ clearMarkdownContextData,
+ ],
+ );
+
+ return (
+
+ {props.children}
+
+ );
+}
+
+export default MarkdownContextProvider;
diff --git a/native/markdown/markdown-context.js b/native/markdown/markdown-context.js
--- a/native/markdown/markdown-context.js
+++ b/native/markdown/markdown-context.js
@@ -2,9 +2,14 @@
import * as React from 'react';
+import type { SetState } from 'lib/types/hook-types';
+
export type MarkdownContextType = {
- +setLinkModalActive: boolean => void,
- +setLinkPressActive: boolean => void,
+ +setLinkModalActive: SetState<{ [key: string]: boolean }>,
+ +linkModalActive: { [key: string]: boolean },
+ +setLinkPressActive: SetState<{ [key: string]: boolean }>,
+ +linkPressActive: { [key: string]: boolean },
+ +clearMarkdownContextData: () => void,
};
const MarkdownContext: React.Context = React.createContext(
diff --git a/native/markdown/markdown-link.react.js b/native/markdown/markdown-link.react.js
--- a/native/markdown/markdown-link.react.js
+++ b/native/markdown/markdown-link.react.js
@@ -1,20 +1,23 @@
// @flow
+import invariant from 'invariant';
import * as React from 'react';
import { Text, Linking, Alert, Platform } from 'react-native';
import { normalizeURL } from 'lib/utils/url-utils';
+import { MessageContext } from '../chat/message-context.react';
import { MarkdownContext, type MarkdownContextType } from './markdown-context';
function useDisplayLinkPrompt(
inputURL: string,
- markdownContext: ?MarkdownContextType,
+ markdownContext: MarkdownContextType,
+ messageKey: string,
) {
- const setLinkModalActive = markdownContext?.setLinkModalActive;
+ const { setLinkModalActive } = markdownContext;
const onDismiss = React.useCallback(() => {
- setLinkModalActive?.(false);
- }, [setLinkModalActive]);
+ setLinkModalActive({ [messageKey]: false });
+ }, [setLinkModalActive, messageKey]);
const url = normalizeURL(inputURL);
const onConfirm = React.useCallback(() => {
@@ -27,7 +30,7 @@
displayURL += '…';
}
return React.useCallback(() => {
- setLinkModalActive && setLinkModalActive(true);
+ setLinkModalActive({ [messageKey]: true });
Alert.alert(
'External link',
`You sure you want to open this link?\n\n${displayURL}`,
@@ -37,7 +40,7 @@
],
{ cancelable: true, onDismiss },
);
- }, [setLinkModalActive, displayURL, onConfirm, onDismiss]);
+ }, [setLinkModalActive, messageKey, displayURL, onConfirm, onDismiss]);
}
type TextProps = React.ElementConfig;
@@ -48,15 +51,22 @@
};
function MarkdownLink(props: Props): React.Node {
const markdownContext = React.useContext(MarkdownContext);
+ invariant(markdownContext, 'MarkdownContext should be set');
+
+ const messageContext = React.useContext(MessageContext);
+ invariant(messageContext, 'MessageContext should be set');
+
+ const { messageKey } = messageContext;
const { target, ...rest } = props;
- const onPressLink = useDisplayLinkPrompt(target, markdownContext);
+ const onPressLink = useDisplayLinkPrompt(target, markdownContext, messageKey);
+
+ const { setLinkPressActive } = markdownContext;
- const setLinkPressActive = markdownContext?.setLinkPressActive;
const androidOnStartShouldSetResponderCapture = React.useCallback(() => {
- setLinkPressActive?.(true);
+ setLinkPressActive({ [messageKey]: true });
return true;
- }, [setLinkPressActive]);
+ }, [setLinkPressActive, messageKey]);
const activePressHasMoved = React.useRef(false);
const androidOnResponderMove = React.useCallback(() => {
@@ -68,8 +78,8 @@
onPressLink();
}
activePressHasMoved.current = false;
- setLinkPressActive?.(false);
- }, [onPressLink, setLinkPressActive]);
+ setLinkPressActive({ [messageKey]: false });
+ }, [onPressLink, setLinkPressActive, messageKey]);
if (Platform.OS !== 'android') {
return ;
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -27,6 +27,7 @@
import ErrorBoundary from './error-boundary.react';
import InputStateContainer from './input/input-state-container.react';
import LifecycleHandler from './lifecycle/lifecycle-handler.react';
+import MarkdownContextProvider from './markdown/markdown-context-provider.react';
import { defaultNavigationState } from './navigation/default-state';
import DisconnectedBarVisibilityHandler from './navigation/disconnected-bar-visibility-handler.react';
import { setGlobalNavContext } from './navigation/icky-global';
@@ -248,23 +249,25 @@
-
-
-
-
- {gated}
-
-
-
-
- {navigation}
-
-
-
+
+
+
+
+
+ {gated}
+
+
+
+
+ {navigation}
+
+
+
+