diff --git a/native/chat/message-result.react.js b/native/chat/message-result.react.js
index a93cacf08..bbc7f4e3e 100644
--- a/native/chat/message-result.react.js
+++ b/native/chat/message-result.react.js
@@ -1,68 +1,73 @@
// @flow
import * as React from 'react';
import { Text, View } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import { type ThreadInfo } from 'lib/types/thread-types.js';
import { longAbsoluteDate } from 'lib/utils/date-utils.js';
+import type { ChatNavigationProp } from './chat.react';
import { MessageListContextProvider } from './message-list-types.js';
import { Message } from './message.react.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import type { NavigationRoute } from '../navigation/route-names';
import { useStyles } from '../themes/colors.js';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
type MessageResultProps = {
+item: ChatMessageInfoItemWithHeight,
+threadInfo: ThreadInfo,
- +navigation: AppNavigationProp<'TogglePinModal'>,
- +route: NavigationRoute<'TogglePinModal'>,
+ +navigation:
+ | AppNavigationProp<'TogglePinModal'>
+ | ChatNavigationProp<'MessageResultsScreen'>,
+ +route:
+ | NavigationRoute<'TogglePinModal'>
+ | NavigationRoute<'MessageResultsScreen'>,
};
function MessageResult(props: MessageResultProps): React.Node {
const styles = useStyles(unboundStyles);
const onToggleFocus = React.useCallback(() => {}, []);
return (
{longAbsoluteDate(props.item.messageInfo.time)}
);
}
const unboundStyles = {
container: {
marginTop: 5,
backgroundColor: 'panelForeground',
overflow: 'scroll',
maxHeight: 400,
},
viewContainer: {
marginTop: 10,
marginBottom: 10,
},
messageDate: {
color: 'messageLabel',
fontSize: 12,
marginLeft: 55,
},
};
export default MessageResult;
diff --git a/native/chat/message-results-screen.react.js b/native/chat/message-results-screen.react.js
index 333b831f3..c768070f2 100644
--- a/native/chat/message-results-screen.react.js
+++ b/native/chat/message-results-screen.react.js
@@ -1,24 +1,179 @@
// @flow
+import invariant from 'invariant';
import * as React from 'react';
+import { View } from 'react-native';
+import { ScrollView } from 'react-native-gesture-handler';
+import { fetchPinnedMessages } from 'lib/actions/message-actions.js';
+import { messageListData } from 'lib/selectors/chat-selectors.js';
+import { createMessageInfo } from 'lib/shared/message-utils.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
+import { useServerCall } from 'lib/utils/action-utils.js';
+import { useHeightMeasurer } from './chat-context.js';
import type { ChatNavigationProp } from './chat.react';
+import MessageResult from './message-result.react.js';
import type { NavigationRoute } from '../navigation/route-names';
+import { useSelector } from '../redux/redux-utils.js';
+import type { ChatMessageItemWithHeight } from '../types/chat-types.js';
export type MessageResultsScreenParams = {
+threadInfo: ThreadInfo,
};
type MessageResultsScreenProps = {
+navigation: ChatNavigationProp<'MessageResultsScreen'>,
+route: NavigationRoute<'MessageResultsScreen'>,
};
-// eslint-disable-next-line no-unused-vars
function MessageResultsScreen(props: MessageResultsScreenProps): React.Node {
- return null;
+ const { navigation, route } = props;
+ const { threadInfo } = route.params;
+ const { id: threadID } = threadInfo;
+ const [rawMessageResults, setRawMessageResults] = React.useState([]);
+
+ const measureMessages = useHeightMeasurer();
+ const [measuredMessages, setMeasuredMessages] = React.useState([]);
+
+ const callFetchPinnedMessages = useServerCall(fetchPinnedMessages);
+ const userInfos = useSelector(state => state.userStore.userInfos);
+
+ React.useEffect(() => {
+ (async () => {
+ const result = await callFetchPinnedMessages({ threadID });
+ setRawMessageResults(result.pinnedMessages);
+ })();
+ }, [callFetchPinnedMessages, threadID]);
+
+ const translatedMessageResults = React.useMemo(() => {
+ const threadInfos = { [threadID]: threadInfo };
+
+ return rawMessageResults
+ .map(messageInfo =>
+ createMessageInfo(messageInfo, null, userInfos, threadInfos),
+ )
+ .filter(Boolean);
+ }, [rawMessageResults, userInfos, threadID, threadInfo]);
+
+ const chatMessageInfos = useSelector(
+ messageListData(threadInfo.id, translatedMessageResults),
+ );
+
+ const sortedUniqueChatMessageInfoItems = React.useMemo(() => {
+ if (!chatMessageInfos) {
+ return [];
+ }
+
+ const chatMessageInfoItems = chatMessageInfos.filter(
+ item => item.itemType === 'message' && item.isPinned,
+ );
+
+ // By the nature of using messageListData and passing in
+ // the desired translatedMessageResults as additional
+ // messages, we will have duplicate ChatMessageInfoItems.
+ const uniqueChatMessageInfoItemsMap = new Map();
+ chatMessageInfoItems.forEach(
+ item =>
+ item.messageInfo &&
+ item.messageInfo.id &&
+ uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item),
+ );
+
+ // Push the items in the order they appear in the rawMessageResults
+ // since the messages fetched from the server are already sorted
+ // in the order of pin_time (newest first).
+ const sortedChatMessageInfoItems = [];
+ for (let i = 0; i < rawMessageResults.length; i++) {
+ sortedChatMessageInfoItems.push(
+ uniqueChatMessageInfoItemsMap.get(rawMessageResults[i].id),
+ );
+ }
+
+ return sortedChatMessageInfoItems.filter(Boolean);
+ }, [chatMessageInfos, rawMessageResults]);
+
+ const measureCallback = React.useCallback(
+ (listDataWithHeights: $ReadOnlyArray) => {
+ setMeasuredMessages(listDataWithHeights);
+ },
+ [],
+ );
+
+ React.useEffect(() => {
+ measureMessages(
+ sortedUniqueChatMessageInfoItems,
+ threadInfo,
+ measureCallback,
+ );
+ }, [
+ measureCallback,
+ measureMessages,
+ sortedUniqueChatMessageInfoItems,
+ threadInfo,
+ ]);
+
+ const modifiedItems = React.useMemo(
+ () =>
+ measuredMessages.map(item => {
+ invariant(item.itemType !== 'loader', 'should not be loader');
+ invariant(
+ item.messageShapeType !== 'robotext',
+ 'should not be robotext',
+ );
+
+ if (item.messageShapeType === 'multimedia') {
+ return {
+ ...item,
+ startsConversation: false,
+ startsCluster: true,
+ endsCluster: true,
+ messageInfo: {
+ ...item.messageInfo,
+ creator: {
+ ...item.messageInfo.creator,
+ isViewer: false,
+ },
+ },
+ };
+ }
+
+ return {
+ ...item,
+ startsConversation: false,
+ startsCluster: true,
+ endsCluster: true,
+ messageInfo: {
+ ...item.messageInfo,
+ creator: {
+ ...item.messageInfo.creator,
+ isViewer: false,
+ },
+ },
+ };
+ }),
+ [measuredMessages],
+ );
+
+ const messageResultsToDisplay = React.useMemo(
+ () =>
+ modifiedItems.map(item => (
+
+ )),
+ [modifiedItems, threadInfo, navigation, route],
+ );
+
+ return (
+
+ {messageResultsToDisplay}
+
+ );
}
export default MessageResultsScreen;
diff --git a/native/chat/message.react.js b/native/chat/message.react.js
index 6aab78708..529285309 100644
--- a/native/chat/message.react.js
+++ b/native/chat/message.react.js
@@ -1,144 +1,148 @@
// @flow
import _isEqual from 'lodash/fp/isEqual.js';
import * as React from 'react';
import {
LayoutAnimation,
TouchableWithoutFeedback,
PixelRatio,
} from 'react-native';
import shallowequal from 'shallowequal';
import { messageKey } from 'lib/shared/message-utils.js';
import type { ChatNavigationProp } from './chat.react.js';
import MultimediaMessage from './multimedia-message.react.js';
import { RobotextMessage } from './robotext-message.react.js';
import { TextMessage } from './text-message.react.js';
import { messageItemHeight } from './utils.js';
import {
type KeyboardState,
KeyboardContext,
} from '../keyboard/keyboard-state.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import type { NavigationRoute } from '../navigation/route-names.js';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
import { type VerticalBounds } from '../types/layout-types.js';
import type { LayoutEvent } from '../types/react-native.js';
type BaseProps = {
+item: ChatMessageInfoItemWithHeight,
+focused: boolean,
+navigation:
| ChatNavigationProp<'MessageList'>
- | AppNavigationProp<'TogglePinModal'>,
- +route: NavigationRoute<'MessageList'> | NavigationRoute<'TogglePinModal'>,
+ | AppNavigationProp<'TogglePinModal'>
+ | ChatNavigationProp<'MessageResultsScreen'>,
+ +route:
+ | NavigationRoute<'MessageList'>
+ | NavigationRoute<'TogglePinModal'>
+ | NavigationRoute<'MessageResultsScreen'>,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
};
type Props = {
...BaseProps,
+keyboardState: ?KeyboardState,
};
class Message extends React.Component {
shouldComponentUpdate(nextProps: Props): boolean {
const { item, ...props } = this.props;
const { item: nextItem, ...newProps } = nextProps;
return !_isEqual(item, nextItem) || !shallowequal(props, newProps);
}
componentDidUpdate(prevProps: Props) {
if (
(prevProps.focused || prevProps.item.startsConversation) !==
(this.props.focused || this.props.item.startsConversation)
) {
LayoutAnimation.easeInEaseOut();
}
}
render() {
let message;
if (this.props.item.messageShapeType === 'text') {
message = (
);
} else if (this.props.item.messageShapeType === 'multimedia') {
message = (
);
} else {
message = (
);
}
const onLayout = __DEV__ ? this.onLayout : undefined;
return (
{message}
);
}
onLayout = (event: LayoutEvent) => {
if (this.props.focused) {
return;
}
const measuredHeight = event.nativeEvent.layout.height;
const expectedHeight = messageItemHeight(this.props.item);
const pixelRatio = 1 / PixelRatio.get();
const distance = Math.abs(measuredHeight - expectedHeight);
if (distance < pixelRatio) {
return;
}
const approxMeasuredHeight = Math.round(measuredHeight * 100) / 100;
const approxExpectedHeight = Math.round(expectedHeight * 100) / 100;
console.log(
`Message height for ${this.props.item.messageShapeType} ` +
`${messageKey(this.props.item.messageInfo)} was expected to be ` +
`${approxExpectedHeight} but is actually ${approxMeasuredHeight}. ` +
"This means MessageList's FlatList isn't getting the right item " +
'height for some of its nodes, which is guaranteed to cause glitchy ' +
'behavior. Please investigate!!',
);
};
dismissKeyboard = () => {
const { keyboardState } = this.props;
keyboardState && keyboardState.dismissKeyboard();
};
}
const ConnectedMessage: React.ComponentType = React.memo(
function ConnectedMessage(props: BaseProps) {
const keyboardState = React.useContext(KeyboardContext);
return ;
},
);
export { ConnectedMessage as Message };
diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js
index b0c05b85b..c81d4e1d1 100644
--- a/native/chat/robotext-message.react.js
+++ b/native/chat/robotext-message.react.js
@@ -1,217 +1,221 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
import { messageKey } from 'lib/shared/message-utils.js';
import { useCanCreateSidebarFromMessage } from 'lib/shared/thread-utils.js';
import { inlineEngagementCenterStyle } from './chat-constants.js';
import type { ChatNavigationProp } from './chat.react.js';
import { InlineEngagement } from './inline-engagement.react.js';
import { InnerRobotextMessage } from './inner-robotext-message.react.js';
import { Timestamp } from './timestamp.react.js';
import { getMessageTooltipKey, useContentAndHeaderOpacity } from './utils.js';
import { ChatContext } from '../chat/chat-context.js';
import { KeyboardContext } from '../keyboard/keyboard-state.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import { OverlayContext } from '../navigation/overlay-context.js';
import { RobotextMessageTooltipModalRouteName } from '../navigation/route-names.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import { useStyles } from '../themes/colors.js';
import { fixedTooltipHeight } from '../tooltip/tooltip.react.js';
import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
import { AnimatedView } from '../types/styles.js';
type Props = {
...React.ElementConfig,
+item: ChatRobotextMessageInfoItemWithHeight,
+navigation:
| ChatNavigationProp<'MessageList'>
- | AppNavigationProp<'TogglePinModal'>,
- +route: NavigationRoute<'MessageList'> | NavigationRoute<'TogglePinModal'>,
+ | AppNavigationProp<'TogglePinModal'>
+ | ChatNavigationProp<'MessageResultsScreen'>,
+ +route:
+ | NavigationRoute<'MessageList'>
+ | NavigationRoute<'TogglePinModal'>
+ | NavigationRoute<'MessageResultsScreen'>,
+focused: boolean,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
};
function RobotextMessage(props: Props): React.Node {
const {
item,
navigation,
route,
focused,
toggleFocus,
verticalBounds,
...viewProps
} = props;
let timestamp = null;
if (focused || item.startsConversation) {
timestamp = (
);
}
const styles = useStyles(unboundStyles);
let inlineEngagement = null;
if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) {
inlineEngagement = (
);
}
const chatContext = React.useContext(ChatContext);
const keyboardState = React.useContext(KeyboardContext);
const key = messageKey(item.messageInfo);
const onPress = React.useCallback(() => {
const didDismiss =
keyboardState && keyboardState.dismissKeyboardIfShowing();
if (!didDismiss) {
toggleFocus(key);
}
}, [keyboardState, toggleFocus, key]);
const overlayContext = React.useContext(OverlayContext);
const viewRef = React.useRef>();
const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage(
item.threadInfo,
item.messageInfo,
);
const visibleEntryIDs = React.useMemo(() => {
const result = [];
if (item.threadCreatedFromMessage || canCreateSidebarFromMessage) {
result.push('sidebar');
}
return result;
}, [item.threadCreatedFromMessage, canCreateSidebarFromMessage]);
const openRobotextTooltipModal = React.useCallback(
(x, y, width, height, pageX, pageY) => {
invariant(
verticalBounds,
'verticalBounds should be present in openRobotextTooltipModal',
);
const coordinates = { x: pageX, y: pageY, width, height };
const messageTop = pageY;
const messageBottom = pageY + height;
const boundsTop = verticalBounds.y;
const boundsBottom = verticalBounds.y + verticalBounds.height;
const belowMargin = 20;
const belowSpace = fixedTooltipHeight + belowMargin;
const { isViewer } = item.messageInfo.creator;
const aboveMargin = isViewer ? 30 : 50;
const aboveSpace = fixedTooltipHeight + aboveMargin;
let margin = 0;
if (
messageBottom + belowSpace > boundsBottom &&
messageTop - aboveSpace > boundsTop
) {
margin = aboveMargin;
}
const currentInputBarHeight =
chatContext?.chatInputBarHeights.get(item.threadInfo.id) ?? 0;
props.navigation.navigate<'RobotextMessageTooltipModal'>({
name: RobotextMessageTooltipModalRouteName,
params: {
presentedFrom: props.route.key,
initialCoordinates: coordinates,
verticalBounds,
visibleEntryIDs,
tooltipLocation: 'fixed',
margin,
item,
chatInputBarHeight: currentInputBarHeight,
},
key: getMessageTooltipKey(item),
});
},
[
item,
props.navigation,
props.route.key,
verticalBounds,
visibleEntryIDs,
chatContext,
],
);
const onLongPress = React.useCallback(() => {
if (keyboardState && keyboardState.dismissKeyboardIfShowing()) {
return;
}
if (visibleEntryIDs.length === 0) {
return;
}
if (!viewRef.current || !verticalBounds) {
return;
}
if (!focused) {
toggleFocus(messageKey(item.messageInfo));
}
invariant(overlayContext, 'RobotextMessage should have OverlayContext');
overlayContext.setScrollBlockingModalStatus('open');
viewRef.current?.measure(openRobotextTooltipModal);
}, [
focused,
item,
keyboardState,
overlayContext,
toggleFocus,
verticalBounds,
viewRef,
visibleEntryIDs,
openRobotextTooltipModal,
]);
const onLayout = React.useCallback(() => {}, []);
const contentAndHeaderOpacity = useContentAndHeaderOpacity(item);
return (
{timestamp}
{inlineEngagement}
);
}
const unboundStyles = {
sidebar: {
marginTop: inlineEngagementCenterStyle.topOffset,
marginBottom: -inlineEngagementCenterStyle.topOffset,
alignSelf: 'center',
},
};
export { RobotextMessage };
diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js
index 745e356ce..2d2a6aa65 100644
--- a/native/chat/text-message.react.js
+++ b/native/chat/text-message.react.js
@@ -1,289 +1,293 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
import { useCanEditMessage } from 'lib/shared/edit-messages-utils.js';
import { messageKey } from 'lib/shared/message-utils.js';
import {
threadHasPermission,
useCanCreateSidebarFromMessage,
} from 'lib/shared/thread-utils.js';
import { threadPermissions } from 'lib/types/thread-types.js';
import type { ChatNavigationProp } from './chat.react.js';
import ComposedMessage from './composed-message.react.js';
import { InnerTextMessage } from './inner-text-message.react.js';
import {
MessagePressResponderContext,
type MessagePressResponderContextType,
} from './message-press-responder-context.js';
import textMessageSendFailed from './text-message-send-failed.js';
import { getMessageTooltipKey } from './utils.js';
import { ChatContext, type ChatContextType } from '../chat/chat-context.js';
import { MarkdownContext } from '../markdown/markdown-context.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import {
OverlayContext,
type OverlayContextType,
} from '../navigation/overlay-context.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import { TextMessageTooltipModalRouteName } from '../navigation/route-names.js';
import { fixedTooltipHeight } from '../tooltip/tooltip.react.js';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
import { useShouldRenderEditButton } from '../utils/edit-messages-utils.js';
type BaseProps = {
...React.ElementConfig,
+item: ChatTextMessageInfoItemWithHeight,
+navigation:
| ChatNavigationProp<'MessageList'>
- | AppNavigationProp<'TogglePinModal'>,
- +route: NavigationRoute<'MessageList'> | NavigationRoute<'TogglePinModal'>,
+ | AppNavigationProp<'TogglePinModal'>
+ | ChatNavigationProp<'MessageResultsScreen'>,
+ +route:
+ | NavigationRoute<'MessageList'>
+ | NavigationRoute<'TogglePinModal'>
+ | NavigationRoute<'MessageResultsScreen'>,
+focused: boolean,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
};
type Props = {
...BaseProps,
// Redux state
+canCreateSidebarFromMessage: boolean,
// withOverlayContext
+overlayContext: ?OverlayContextType,
// ChatContext
+chatContext: ?ChatContextType,
// MarkdownContext
+isLinkModalActive: boolean,
+canEditMessage: boolean,
+shouldRenderEditButton: boolean,
+canTogglePins: boolean,
};
class TextMessage extends React.PureComponent {
message: ?React.ElementRef;
messagePressResponderContext: MessagePressResponderContextType;
constructor(props: Props) {
super(props);
this.messagePressResponderContext = {
onPressMessage: this.onPress,
};
}
render() {
const {
item,
navigation,
route,
focused,
toggleFocus,
verticalBounds,
overlayContext,
chatContext,
isLinkModalActive,
canCreateSidebarFromMessage,
canEditMessage,
shouldRenderEditButton,
canTogglePins,
...viewProps
} = this.props;
let swipeOptions = 'none';
const canReply = this.canReply();
const canNavigateToSidebar = this.canNavigateToSidebar();
if (isLinkModalActive) {
swipeOptions = 'none';
} else if (canReply && canNavigateToSidebar) {
swipeOptions = 'both';
} else if (canReply) {
swipeOptions = 'reply';
} else if (canNavigateToSidebar) {
swipeOptions = 'sidebar';
}
return (
);
}
messageRef = (message: ?React.ElementRef) => {
this.message = message;
};
canReply() {
return threadHasPermission(
this.props.item.threadInfo,
threadPermissions.VOICED,
);
}
canNavigateToSidebar() {
return (
this.props.item.threadCreatedFromMessage ||
this.props.canCreateSidebarFromMessage
);
}
visibleEntryIDs() {
const result = ['copy'];
if (this.canReply()) {
result.push('reply');
}
if (this.props.canEditMessage && this.props.shouldRenderEditButton) {
result.push('edit');
}
if (this.props.canTogglePins) {
this.props.item.isPinned ? result.push('unpin') : result.push('pin');
}
if (
this.props.item.threadCreatedFromMessage ||
this.props.canCreateSidebarFromMessage
) {
result.push('sidebar');
}
if (!this.props.item.messageInfo.creator.isViewer) {
result.push('report');
}
return result;
}
onPress = () => {
const visibleEntryIDs = this.visibleEntryIDs();
if (visibleEntryIDs.length === 0) {
return;
}
const {
message,
props: { verticalBounds, isLinkModalActive },
} = this;
if (!message || !verticalBounds || isLinkModalActive) {
return;
}
const { focused, toggleFocus, item } = this.props;
if (!focused) {
toggleFocus(messageKey(item.messageInfo));
}
const { overlayContext } = this.props;
invariant(overlayContext, 'TextMessage should have OverlayContext');
overlayContext.setScrollBlockingModalStatus('open');
message.measure((x, y, width, height, pageX, pageY) => {
const coordinates = { x: pageX, y: pageY, width, height };
const messageTop = pageY;
const messageBottom = pageY + height;
const boundsTop = verticalBounds.y;
const boundsBottom = verticalBounds.y + verticalBounds.height;
const belowMargin = 20;
const belowSpace = fixedTooltipHeight + belowMargin;
const { isViewer } = item.messageInfo.creator;
const aboveMargin = isViewer ? 30 : 50;
const aboveSpace = fixedTooltipHeight + aboveMargin;
let margin = belowMargin;
if (
messageBottom + belowSpace > boundsBottom &&
messageTop - aboveSpace > boundsTop
) {
margin = aboveMargin;
}
const currentInputBarHeight =
this.props.chatContext?.chatInputBarHeights.get(item.threadInfo.id) ??
0;
this.props.navigation.navigate<'TextMessageTooltipModal'>({
name: TextMessageTooltipModalRouteName,
params: {
presentedFrom: this.props.route.key,
initialCoordinates: coordinates,
verticalBounds,
visibleEntryIDs,
tooltipLocation: 'fixed',
margin,
item,
chatInputBarHeight: currentInputBarHeight,
},
key: getMessageTooltipKey(item),
});
});
};
}
const ConnectedTextMessage: React.ComponentType =
React.memo(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, clearMarkdownContextData } = markdownContext;
const key = messageKey(props.item.messageInfo);
// We check if there is an key in the object - 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 isLinkModalActive = linkModalActive[key] ?? false;
const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage(
props.item.threadInfo,
props.item.messageInfo,
);
const shouldRenderEditButton = useShouldRenderEditButton();
const canEditMessage = useCanEditMessage(
props.item.threadInfo,
props.item.messageInfo,
);
const canTogglePins = threadHasPermission(
props.item.threadInfo,
threadPermissions.MANAGE_PINS,
);
React.useEffect(() => clearMarkdownContextData, [clearMarkdownContextData]);
return (
);
});
export { ConnectedTextMessage as TextMessage };