diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
index 086a0ee571c1..6d16920b164e 100644
--- a/.storybook/preview-head.html
+++ b/.storybook/preview-head.html
@@ -50,5 +50,9 @@
hasCustomTitleBar: () => false,
},
};
+
+ window.ConversationController = window.ConversationController || {};
+ window.ConversationController.isSignalConversation = () => false;
+ window.ConversationController.onConvoMessageMount = noop;
window.getPreferredSystemLocales = () => ['en'];
diff --git a/ts/components/EditHistoryMessagesModal.tsx b/ts/components/EditHistoryMessagesModal.tsx
index 9a57b8b50ea2..42745a550789 100644
--- a/ts/components/EditHistoryMessagesModal.tsx
+++ b/ts/components/EditHistoryMessagesModal.tsx
@@ -19,6 +19,7 @@ export type PropsType = {
editHistoryMessages: Array;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
+ platform: string;
kickOffAttachmentDownload: (options: {
attachment: AttachmentType;
messageId: string;
@@ -70,6 +71,7 @@ export function EditHistoryMessagesModal({
getPreferredBadge,
editHistoryMessages,
i18n,
+ platform,
kickOffAttachmentDownload,
showLightbox,
}: PropsType): JSX.Element {
@@ -100,6 +102,7 @@ export function EditHistoryMessagesModal({
containerElementRef={containerElementRef}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
+ platform={platform}
key={messageAttributes.timestamp}
kickOffAttachmentDownload={kickOffAttachmentDownload}
showLightbox={closeAndShowLightbox}
diff --git a/ts/components/GroupV1MigrationDialog.tsx b/ts/components/GroupV1MigrationDialog.tsx
index a30a9aa4ee04..72aafb47d2dd 100644
--- a/ts/components/GroupV1MigrationDialog.tsx
+++ b/ts/components/GroupV1MigrationDialog.tsx
@@ -7,7 +7,7 @@ import type { ConversationType } from '../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import { GroupDialog } from './GroupDialog';
import { sortByTitle } from '../util/sortByTitle';
-import { missingCaseError } from '../util';
+import { missingCaseError } from '../util/missingCaseError';
export type DataPropsType = {
conversationId: string;
diff --git a/ts/components/StoryViewer.stories.tsx b/ts/components/StoryViewer.stories.tsx
index aeecb86f6f41..254d3dad947a 100644
--- a/ts/components/StoryViewer.stories.tsx
+++ b/ts/components/StoryViewer.stories.tsx
@@ -39,6 +39,9 @@ export default {
i18n: {
defaultValue: i18n,
},
+ platform: {
+ defaultValue: 'darwin',
+ },
loadStoryReplies: { action: true },
markStoryRead: { action: true },
numStories: {
diff --git a/ts/components/StoryViewer.tsx b/ts/components/StoryViewer.tsx
index c569ff379c53..7f909b919d3d 100644
--- a/ts/components/StoryViewer.tsx
+++ b/ts/components/StoryViewer.tsx
@@ -101,6 +101,7 @@ export type PropsType = {
) => unknown;
onUseEmoji: (_: EmojiPickDataType) => unknown;
onMediaPlaybackStart: () => void;
+ platform: string;
preferredReactionEmoji: ReadonlyArray;
queueStoryDownload: (storyId: string) => unknown;
recentEmojis?: ReadonlyArray;
@@ -155,6 +156,7 @@ export function StoryViewer({
onTextTooLong,
onUseEmoji,
onMediaPlaybackStart,
+ platform,
preferredReactionEmoji,
queueStoryDownload,
recentEmojis,
@@ -914,6 +916,7 @@ export function StoryViewer({
hasViewReceiptSetting={hasViewReceiptSetting}
hasViewsCapability={isSent}
i18n={i18n}
+ platform={platform}
isInternalUser={isInternalUser}
group={group}
onClose={() => setCurrentViewTarget(null)}
diff --git a/ts/components/StoryViewsNRepliesModal.stories.tsx b/ts/components/StoryViewsNRepliesModal.stories.tsx
index c6c20db36810..88e495398481 100644
--- a/ts/components/StoryViewsNRepliesModal.stories.tsx
+++ b/ts/components/StoryViewsNRepliesModal.stories.tsx
@@ -39,6 +39,9 @@ export default {
i18n: {
defaultValue: i18n,
},
+ platform: {
+ defaultValue: 'darwin',
+ },
onClose: { action: true },
onSetSkinTone: { action: true },
onReact: { action: true },
diff --git a/ts/components/StoryViewsNRepliesModal.tsx b/ts/components/StoryViewsNRepliesModal.tsx
index 9d34b4b713fc..8c427407c700 100644
--- a/ts/components/StoryViewsNRepliesModal.tsx
+++ b/ts/components/StoryViewsNRepliesModal.tsx
@@ -87,6 +87,7 @@ export type PropsType = {
hasViewReceiptSetting: boolean;
hasViewsCapability: boolean;
i18n: LocalizerType;
+ platform: string;
isInternalUser?: boolean;
onChangeViewTarget: (target: StoryViewTargetType) => unknown;
onClose: () => unknown;
@@ -120,6 +121,7 @@ export function StoryViewsNRepliesModal({
hasViewReceiptSetting,
hasViewsCapability,
i18n,
+ platform,
isInternalUser,
onChangeViewTarget,
onClose,
@@ -286,6 +288,7 @@ export function StoryViewsNRepliesModal({
}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
+ platform={platform}
id={reply.id}
isInternalUser={isInternalUser}
reply={reply}
@@ -463,6 +466,7 @@ type ReplyOrReactionMessageProps = {
deleteGroupStoryReplyForEveryone: (replyId: string) => void;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
+ platform: string;
id: string;
isInternalUser?: boolean;
onContextMenu?: (ev: React.MouseEvent) => void;
@@ -481,6 +485,7 @@ function ReplyOrReactionMessage({
deleteGroupStoryReplyForEveryone,
containerElementRef,
getPreferredBadge,
+ platform,
shouldCollapseAbove,
shouldCollapseBelow,
showContactModal,
@@ -549,6 +554,7 @@ function ReplyOrReactionMessage({
onContextMenu={onContextMenu}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
+ platform={platform}
id={reply.id}
interactionMode="mouse"
readStatus={reply.readStatus}
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index 0c7133ada2ec..c508c9b679f9 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -98,7 +98,6 @@ import { Emojify } from './Emojify';
import { getPaymentEventDescription } from '../../messages/helpers';
import { PanelType } from '../../types/Panels';
import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser';
-import { isMacOS } from '../../OS';
const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 16;
const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18;
@@ -297,6 +296,7 @@ export type PropsHousekeeping = {
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
interactionMode: InteractionModeType;
+ platform: string;
renderAudioAttachment: (props: AudioAttachmentProps) => JSX.Element;
shouldCollapseAbove: boolean;
shouldCollapseBelow: boolean;
@@ -2577,6 +2577,7 @@ export class Message extends React.PureComponent {
isSelected,
isSelectMode,
onKeyDown,
+ platform,
renderMenu,
shouldCollapseAbove,
shouldCollapseBelow,
@@ -2584,6 +2585,7 @@ export class Message extends React.PureComponent {
onToggleSelect,
onReplyToMessage,
} = this.props;
+ const isMacOS = platform === 'darwin';
const { expired, expiring, isTargeted, imageBroken } = this.state;
if (expired) {
@@ -2622,7 +2624,7 @@ export class Message extends React.PureComponent {
// We use `onClickCapture` here and preven default/stop propagation to
// prevent other click handlers from firing.
onClickCapture: event => {
- if (isMacOS() ? event.metaKey : event.ctrlKey) {
+ if (isMacOS ? event.metaKey : event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
onToggleSelect(true, false);
diff --git a/ts/components/conversation/MessageDetail.stories.tsx b/ts/components/conversation/MessageDetail.stories.tsx
index 5424780de95d..009711dfa9cc 100644
--- a/ts/components/conversation/MessageDetail.stories.tsx
+++ b/ts/components/conversation/MessageDetail.stories.tsx
@@ -68,6 +68,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({
getPreferredBadge: () => getFakeBadge(),
i18n,
+ platform: 'darwin',
interactionMode: 'keyboard',
theme: ThemeType.light,
diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx
index fa44bf7e5ef9..28a25b98b505 100644
--- a/ts/components/conversation/MessageDetail.tsx
+++ b/ts/components/conversation/MessageDetail.tsx
@@ -67,6 +67,7 @@ export type PropsData = {
sentAt: number;
i18n: LocalizerType;
+ platform: string;
theme: ThemeType;
getPreferredBadge: PreferredBadgeSelectorType;
} & Pick;
@@ -303,6 +304,7 @@ export class MessageDetail extends React.Component {
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
openGiftBadge,
+ platform,
pushPanelForConversation,
renderAudioAttachment,
saveAttachment,
@@ -346,6 +348,7 @@ export class MessageDetail extends React.Component {
kickOffAttachmentDownload={kickOffAttachmentDownload}
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
messageExpanded={noop}
+ platform={platform}
showConversation={showConversation}
openGiftBadge={openGiftBadge}
pushPanelForConversation={pushPanelForConversation}
diff --git a/ts/components/conversation/Quote.stories.tsx b/ts/components/conversation/Quote.stories.tsx
index 951cc3dfc38b..08735c2953f5 100644
--- a/ts/components/conversation/Quote.stories.tsx
+++ b/ts/components/conversation/Quote.stories.tsx
@@ -44,6 +44,9 @@ export default {
i18n: {
defaultValue: i18n,
},
+ platform: {
+ defautlValue: 'darwin',
+ },
isFromMe: {
control: { type: 'checkbox' },
defaultValue: false,
@@ -103,6 +106,7 @@ const defaultMessageProps: TimelineMessagesProps = {
),
getPreferredBadge: () => undefined,
i18n,
+ platform: 'darwin',
id: 'messageId',
// renderingContext: 'storybook',
interactionMode: 'keyboard',
diff --git a/ts/components/conversation/Timeline.stories.tsx b/ts/components/conversation/Timeline.stories.tsx
index 7d9c6203411f..1fcd2e4bed9b 100644
--- a/ts/components/conversation/Timeline.stories.tsx
+++ b/ts/components/conversation/Timeline.stories.tsx
@@ -344,6 +344,7 @@ const renderItem = ({
interactionMode="keyboard"
isNextItemCallingNotification={false}
theme={ThemeType.light}
+ platform="darwin"
containerElementRef={containerElementRef}
containerWidthBreakpoint={containerWidthBreakpoint}
conversationId=""
diff --git a/ts/components/conversation/TimelineItem.stories.tsx b/ts/components/conversation/TimelineItem.stories.tsx
index 8e5606927a5c..d1ae525b09f0 100644
--- a/ts/components/conversation/TimelineItem.stories.tsx
+++ b/ts/components/conversation/TimelineItem.stories.tsx
@@ -61,6 +61,7 @@ const getDefaultProps = () => ({
isTargeted: false,
interactionMode: 'keyboard' as const,
theme: ThemeType.light,
+ platform: 'darwin',
targetMessage: action('targetMessage'),
toggleSelectMessage: action('toggleSelectMessage'),
reactToMessage: action('reactToMessage'),
diff --git a/ts/components/conversation/TimelineItem.tsx b/ts/components/conversation/TimelineItem.tsx
index b9f623b4e226..2b7e8ba8eb33 100644
--- a/ts/components/conversation/TimelineItem.tsx
+++ b/ts/components/conversation/TimelineItem.tsx
@@ -151,6 +151,7 @@ type PropsLocalType = {
isTargeted: boolean;
targetMessage: (messageId: string, conversationId: string) => unknown;
shouldRenderDateHeader: boolean;
+ platform: string;
renderContact: SmartContactRendererType;
renderUniversalTimerNotification: () => JSX.Element;
i18n: LocalizerType;
@@ -188,6 +189,7 @@ export class TimelineItem extends React.PureComponent {
isNextItemCallingNotification,
isTargeted,
item,
+ platform,
renderUniversalTimerNotification,
returnToActiveCall,
targetMessage,
@@ -223,6 +225,7 @@ export class TimelineItem extends React.PureComponent {
shouldHideMetadata={shouldHideMetadata}
containerElementRef={containerElementRef}
getPreferredBadge={getPreferredBadge}
+ platform={platform}
i18n={i18n}
theme={theme}
/>
diff --git a/ts/components/conversation/TimelineMessage.stories.tsx b/ts/components/conversation/TimelineMessage.stories.tsx
index 4af46ce6d713..22f6f5a0cdb6 100644
--- a/ts/components/conversation/TimelineMessage.stories.tsx
+++ b/ts/components/conversation/TimelineMessage.stories.tsx
@@ -281,6 +281,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({
getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined),
giftBadge: overrideProps.giftBadge,
i18n,
+ platform: 'darwin',
id: text('id', overrideProps.id || 'random-message-id'),
// renderingContext: 'storybook',
interactionMode: overrideProps.interactionMode || 'keyboard',
diff --git a/ts/components/emoji/EmojiPicker.tsx b/ts/components/emoji/EmojiPicker.tsx
index ca74e64c7447..eab15c438af6 100644
--- a/ts/components/emoji/EmojiPicker.tsx
+++ b/ts/components/emoji/EmojiPicker.tsx
@@ -24,7 +24,7 @@ import { Emoji } from './Emoji';
import { dataByCategory, search } from './lib';
import type { LocalizerType } from '../../types/Util';
import { isSingleGrapheme } from '../../util/grapheme';
-import { missingCaseError } from '../../util';
+import { missingCaseError } from '../../util/missingCaseError';
export type EmojiPickDataType = {
skinTone?: number;
diff --git a/ts/state/smart/EditHistoryMessagesModal.tsx b/ts/state/smart/EditHistoryMessagesModal.tsx
index dcfb183ecbc7..163d94da552a 100644
--- a/ts/state/smart/EditHistoryMessagesModal.tsx
+++ b/ts/state/smart/EditHistoryMessagesModal.tsx
@@ -7,7 +7,7 @@ import type { GlobalModalsStateType } from '../ducks/globalModals';
import type { MessageAttributesType } from '../../model-types.d';
import type { StateType } from '../reducer';
import { EditHistoryMessagesModal } from '../../components/EditHistoryMessagesModal';
-import { getIntl } from '../selectors/user';
+import { getIntl, getPlatform } from '../selectors/user';
import { getMessagePropsSelector } from '../selectors/message';
import { getPreferredBadgeSelector } from '../selectors/badges';
import { useConversationsActions } from '../ducks/conversations';
@@ -17,6 +17,7 @@ import { strictAssert } from '../../util/assert';
export function SmartEditHistoryMessagesModal(): JSX.Element {
const i18n = useSelector(getIntl);
+ const platform = useSelector(getPlatform);
const { closeEditHistoryModal } = useGlobalModalActions();
@@ -50,6 +51,7 @@ export function SmartEditHistoryMessagesModal(): JSX.Element {
editHistoryMessages={editHistoryMessages}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
+ platform={platform}
kickOffAttachmentDownload={kickOffAttachmentDownload}
showLightbox={showLightbox}
/>
diff --git a/ts/state/smart/MessageDetail.tsx b/ts/state/smart/MessageDetail.tsx
index f1ca063427c2..cca3bb819a52 100644
--- a/ts/state/smart/MessageDetail.tsx
+++ b/ts/state/smart/MessageDetail.tsx
@@ -7,7 +7,12 @@ import { useSelector } from 'react-redux';
import type { Props as MessageDetailProps } from '../../components/conversation/MessageDetail';
import { MessageDetail } from '../../components/conversation/MessageDetail';
import { getContactNameColorSelector } from '../selectors/conversations';
-import { getIntl, getInteractionMode, getTheme } from '../selectors/user';
+import {
+ getIntl,
+ getInteractionMode,
+ getTheme,
+ getPlatform,
+} from '../selectors/user';
import { getMessageDetails } from '../selectors/message';
import { getPreferredBadgeSelector } from '../selectors/badges';
import { renderAudioAttachment } from './renderAudioAttachment';
@@ -27,6 +32,7 @@ export function SmartMessageDetail(): JSX.Element | null {
const getContactNameColor = useSelector(getContactNameColorSelector);
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const i18n = useSelector(getIntl);
+ const platform = useSelector(getPlatform);
const interactionMode = useSelector(getInteractionMode);
const messageDetails = useSelector(getMessageDetails);
const theme = useSelector(getTheme);
@@ -76,6 +82,7 @@ export function SmartMessageDetail(): JSX.Element | null {
errors={errors}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
+ platform={platform}
interactionMode={interactionMode}
kickOffAttachmentDownload={kickOffAttachmentDownload}
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
diff --git a/ts/state/smart/StoryViewer.tsx b/ts/state/smart/StoryViewer.tsx
index 7b2d1079e5e7..bad08f96989f 100644
--- a/ts/state/smart/StoryViewer.tsx
+++ b/ts/state/smart/StoryViewer.tsx
@@ -18,7 +18,7 @@ import {
getPreferredReactionEmoji,
isInternalUser,
} from '../selectors/items';
-import { getIntl } from '../selectors/user';
+import { getIntl, getPlatform } from '../selectors/user';
import { getPreferredBadgeSelector } from '../selectors/badges';
import {
getSelectedStoryData,
@@ -56,6 +56,7 @@ export function SmartStoryViewer(): JSX.Element | null {
const isWindowActive = useIsWindowActive();
const i18n = useSelector(getIntl);
+ const platform = useSelector(getPlatform);
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const preferredReactionEmoji = useSelector>(
getPreferredReactionEmoji
@@ -111,6 +112,7 @@ export function SmartStoryViewer(): JSX.Element | null {
hasAllStoriesUnmuted={hasAllStoriesUnmuted}
hasViewReceiptSetting={hasViewReceiptSetting}
i18n={i18n}
+ platform={platform}
isInternalUser={internalUser}
saveAttachment={internalUser ? saveAttachment : asyncShouldNeverBeCalled}
isSignalConversation={isSignalConversation({
diff --git a/ts/state/smart/TimelineItem.tsx b/ts/state/smart/TimelineItem.tsx
index 262a37de92c2..318b6e6a7037 100644
--- a/ts/state/smart/TimelineItem.tsx
+++ b/ts/state/smart/TimelineItem.tsx
@@ -16,7 +16,12 @@ import { useLightboxActions } from '../ducks/lightbox';
import { useStoriesActions } from '../ducks/stories';
import { useCallingActions } from '../ducks/calling';
import { getPreferredBadgeSelector } from '../selectors/badges';
-import { getIntl, getInteractionMode, getTheme } from '../selectors/user';
+import {
+ getIntl,
+ getInteractionMode,
+ getTheme,
+ getPlatform,
+} from '../selectors/user';
import { getTargetedMessage } from '../selectors/conversations';
import { getTimelineItem } from '../selectors/timeline';
import {
@@ -67,6 +72,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const interactionMode = useSelector(getInteractionMode);
const theme = useSelector(getTheme);
+ const platform = useSelector(getPlatform);
const item = useProxySelector(getTimelineItem, messageId);
const previousItem = useProxySelector(getTimelineItem, previousMessageId);
const nextItem = useProxySelector(getTimelineItem, nextMessageId);
@@ -166,6 +172,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
i18n={i18n}
interactionMode={interactionMode}
theme={theme}
+ platform={platform}
blockGroupLinkRequests={blockGroupLinkRequests}
checkForAccount={checkForAccount}
clearTargetedMessage={clearSelectedMessage}