Removes Inbox Backbone view
This commit is contained in:
parent
603b76c3d9
commit
aa23c2def2
44 changed files with 496 additions and 808 deletions
|
@ -862,9 +862,6 @@
|
||||||
"message": "what's new",
|
"message": "what's new",
|
||||||
"description": "Clickable link that displays the latest release notes"
|
"description": "Clickable link that displays the latest release notes"
|
||||||
},
|
},
|
||||||
"selectAContact": {
|
|
||||||
"message": "Select a contact or group to start chatting."
|
|
||||||
},
|
|
||||||
"typingAlt": {
|
"typingAlt": {
|
||||||
"message": "Typing animation for this conversation",
|
"message": "Typing animation for this conversation",
|
||||||
"description": "Used as the 'title' attribute for the typing animation"
|
"description": "Used as the 'title' attribute for the typing animation"
|
||||||
|
|
|
@ -106,24 +106,6 @@
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="two-column">
|
|
||||||
<div class='module-title-bar-drag-area'></div>
|
|
||||||
|
|
||||||
<div class='left-pane-placeholder'></div>
|
|
||||||
|
|
||||||
<div class='conversation-stack'>
|
|
||||||
<div class='no-conversation-open'>
|
|
||||||
<div class="module-splash-screen__logo module-img--128 module-logo-blue"></div>
|
|
||||||
<h3>{{ welcomeToSignal }}</h3>
|
|
||||||
<p class="whats-new-placeholder"></p>
|
|
||||||
<p>{{ selectAContact }}</p>
|
|
||||||
</div>
|
|
||||||
<div id="toast"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='lightbox-container'></div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="conversation">
|
<script type="text/x-tmpl-mustache" id="conversation">
|
||||||
<div class="ConversationView__template"></div>
|
<div class="ConversationView__template"></div>
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -26,24 +26,6 @@
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="two-column">
|
|
||||||
<div class='module-title-bar-drag-area'></div>
|
|
||||||
|
|
||||||
<div class='left-pane-placeholder'></div>
|
|
||||||
|
|
||||||
<div class='conversation-stack'>
|
|
||||||
<div class='no-conversation-open'>
|
|
||||||
<div class="module-splash-screen__logo module-img--128 module-logo-blue"></div>
|
|
||||||
<h3>{{ welcomeToSignal }}</h3>
|
|
||||||
<p class="whats-new-placeholder"></p>
|
|
||||||
<p>{{ selectAContact }}</p>
|
|
||||||
</div>
|
|
||||||
<div id="toast"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='lightbox-container'></div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="conversation">
|
<script type="text/x-tmpl-mustache" id="conversation">
|
||||||
<div id="ConversationView__template"></div>
|
<div id="ConversationView__template"></div>
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// Copyright 2018-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import * as Views from './views';
|
|
||||||
|
|
||||||
export { Views };
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2018-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
export const show = (element: HTMLElement): void => {
|
|
||||||
const container: HTMLDivElement | null = document.querySelector(
|
|
||||||
'.lightbox-container'
|
|
||||||
);
|
|
||||||
if (!container) {
|
|
||||||
throw new TypeError("'.lightbox-container' is required");
|
|
||||||
}
|
|
||||||
container.innerHTML = '';
|
|
||||||
container.style.display = 'block';
|
|
||||||
container.appendChild(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hide = (): void => {
|
|
||||||
const container: HTMLDivElement | null = document.querySelector(
|
|
||||||
'.lightbox-container'
|
|
||||||
);
|
|
||||||
if (!container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
container.innerHTML = '';
|
|
||||||
container.style.display = 'none';
|
|
||||||
};
|
|
|
@ -1,6 +0,0 @@
|
||||||
// Copyright 2018-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import * as Lightbox from './Lightbox';
|
|
||||||
|
|
||||||
export { Lightbox };
|
|
|
@ -1508,6 +1508,10 @@ export async function startApp(): Promise<void> {
|
||||||
(key === 'c' || key === 'C')
|
(key === 'c' || key === 'C')
|
||||||
) {
|
) {
|
||||||
conversation.trigger('unload', 'keyboard shortcut close');
|
conversation.trigger('unload', 'keyboard shortcut close');
|
||||||
|
window.reduxActions.conversations.showConversation({
|
||||||
|
conversationId: undefined,
|
||||||
|
messageId: undefined,
|
||||||
|
});
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -48,30 +48,35 @@ export const App = ({
|
||||||
appView,
|
appView,
|
||||||
cancelConversationVerification,
|
cancelConversationVerification,
|
||||||
conversationsStoppingSend,
|
conversationsStoppingSend,
|
||||||
hasInitialLoadCompleted,
|
executeMenuAction,
|
||||||
|
executeMenuRole,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
|
hasInitialLoadCompleted,
|
||||||
i18n,
|
i18n,
|
||||||
isCustomizingPreferredReactions,
|
isCustomizingPreferredReactions,
|
||||||
isShowingStoriesView,
|
|
||||||
isMaximized,
|
|
||||||
isFullScreen,
|
isFullScreen,
|
||||||
|
isMaximized,
|
||||||
|
isShowingStoriesView,
|
||||||
isWindows11,
|
isWindows11,
|
||||||
menuOptions,
|
|
||||||
platform,
|
|
||||||
localeMessages,
|
localeMessages,
|
||||||
|
menuOptions,
|
||||||
|
openInbox,
|
||||||
|
platform,
|
||||||
|
registerSingleDevice,
|
||||||
renderCallManager,
|
renderCallManager,
|
||||||
renderCustomizingPreferredReactionsModal,
|
renderCustomizingPreferredReactionsModal,
|
||||||
renderGlobalModalContainer,
|
renderGlobalModalContainer,
|
||||||
|
renderLeftPane,
|
||||||
renderSafetyNumber,
|
renderSafetyNumber,
|
||||||
openInbox,
|
|
||||||
renderStories,
|
renderStories,
|
||||||
requestVerification,
|
requestVerification,
|
||||||
registerSingleDevice,
|
selectedConversationId,
|
||||||
|
selectedMessage,
|
||||||
|
showConversation,
|
||||||
|
showWhatsNewModal,
|
||||||
theme,
|
theme,
|
||||||
verifyConversationsStoppingSend,
|
|
||||||
executeMenuAction,
|
|
||||||
executeMenuRole,
|
|
||||||
titleBarDoubleClick,
|
titleBarDoubleClick,
|
||||||
|
verifyConversationsStoppingSend,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
let contents;
|
let contents;
|
||||||
|
|
||||||
|
@ -101,7 +106,12 @@ export const App = ({
|
||||||
renderCustomizingPreferredReactionsModal={
|
renderCustomizingPreferredReactionsModal={
|
||||||
renderCustomizingPreferredReactionsModal
|
renderCustomizingPreferredReactionsModal
|
||||||
}
|
}
|
||||||
|
renderLeftPane={renderLeftPane}
|
||||||
renderSafetyNumber={renderSafetyNumber}
|
renderSafetyNumber={renderSafetyNumber}
|
||||||
|
selectedConversationId={selectedConversationId}
|
||||||
|
selectedMessage={selectedMessage}
|
||||||
|
showConversation={showConversation}
|
||||||
|
showWhatsNewModal={showWhatsNewModal}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
verifyConversationsStoppingSend={verifyConversationsStoppingSend}
|
verifyConversationsStoppingSend={verifyConversationsStoppingSend}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -79,8 +79,8 @@ const Wrapper = ({
|
||||||
getPreferredBadge={() => undefined}
|
getPreferredBadge={() => undefined}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id={id}
|
id={id}
|
||||||
openConversationInternal={action('openConversationInternal')}
|
|
||||||
sentAt={1587358800000}
|
sentAt={1587358800000}
|
||||||
|
showConversation={action('showConversation')}
|
||||||
snippet="Lorem <<left>>ipsum<<right>> wow"
|
snippet="Lorem <<left>>ipsum<<right>> wow"
|
||||||
theme={ThemeType.light}
|
theme={ThemeType.light}
|
||||||
to={defaultConversations[1]}
|
to={defaultConversations[1]}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { ScrollBehavior } from '../types/Util';
|
||||||
import { getConversationListWidthBreakpoint } from './_util';
|
import { getConversationListWidthBreakpoint } from './_util';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { LookupConversationWithoutUuidActionsType } from '../util/lookupConversationWithoutUuid';
|
import type { LookupConversationWithoutUuidActionsType } from '../util/lookupConversationWithoutUuid';
|
||||||
|
import type { ShowConversationType } from '../state/ducks/conversations';
|
||||||
|
|
||||||
import type { PropsData as ConversationListItemPropsType } from './conversationList/ConversationListItem';
|
import type { PropsData as ConversationListItemPropsType } from './conversationList/ConversationListItem';
|
||||||
import { ConversationListItem } from './conversationList/ConversationListItem';
|
import { ConversationListItem } from './conversationList/ConversationListItem';
|
||||||
|
@ -154,7 +155,7 @@ export type PropsType = {
|
||||||
onSelectConversation: (conversationId: string, messageId?: string) => void;
|
onSelectConversation: (conversationId: string, messageId?: string) => void;
|
||||||
renderMessageSearchResult: (id: string) => JSX.Element;
|
renderMessageSearchResult: (id: string) => JSX.Element;
|
||||||
showChooseGroupMembers: () => void;
|
showChooseGroupMembers: () => void;
|
||||||
showConversation: (conversationId: string) => void;
|
showConversation: ShowConversationType;
|
||||||
} & LookupConversationWithoutUuidActionsType;
|
} & LookupConversationWithoutUuidActionsType;
|
||||||
|
|
||||||
const NORMAL_ROW_HEIGHT = 76;
|
const NORMAL_ROW_HEIGHT = 76;
|
||||||
|
|
|
@ -2,22 +2,25 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import type * as Backbone from 'backbone';
|
|
||||||
import type { SafetyNumberProps } from './SafetyNumberChangeDialog';
|
import type { ConversationModel } from '../models/conversations';
|
||||||
import { SafetyNumberChangeDialog } from './SafetyNumberChangeDialog';
|
import type {
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
ConversationType,
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
ShowConversationType,
|
||||||
|
} from '../state/ducks/conversations';
|
||||||
|
import type { ConversationView } from '../views/conversation_view';
|
||||||
import type { LocalizerType, ThemeType } from '../types/Util';
|
import type { LocalizerType, ThemeType } from '../types/Util';
|
||||||
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
|
import type { SafetyNumberProps } from './SafetyNumberChangeDialog';
|
||||||
|
|
||||||
type InboxViewType = Backbone.View & {
|
import * as log from '../logging/log';
|
||||||
onEmpty?: () => void;
|
import { SECOND } from '../util/durations';
|
||||||
};
|
import { SafetyNumberChangeDialog } from './SafetyNumberChangeDialog';
|
||||||
|
import { ToastStickerPackInstallFailed } from './ToastStickerPackInstallFailed';
|
||||||
type InboxViewOptionsType = Backbone.ViewOptions & {
|
import { WhatsNewLink } from './WhatsNewLink';
|
||||||
initialLoadComplete: boolean;
|
import { showToast } from '../util/showToast';
|
||||||
window: typeof window;
|
import { strictAssert } from '../util/assert';
|
||||||
};
|
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
cancelConversationVerification: () => void;
|
cancelConversationVerification: () => void;
|
||||||
|
@ -27,7 +30,12 @@ export type PropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isCustomizingPreferredReactions: boolean;
|
isCustomizingPreferredReactions: boolean;
|
||||||
renderCustomizingPreferredReactionsModal: () => JSX.Element;
|
renderCustomizingPreferredReactionsModal: () => JSX.Element;
|
||||||
|
renderLeftPane: () => JSX.Element;
|
||||||
renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element;
|
renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element;
|
||||||
|
selectedConversationId?: string;
|
||||||
|
selectedMessage?: string;
|
||||||
|
showConversation: ShowConversationType;
|
||||||
|
showWhatsNewModal: () => unknown;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
verifyConversationsStoppingSend: () => void;
|
verifyConversationsStoppingSend: () => void;
|
||||||
};
|
};
|
||||||
|
@ -40,38 +48,182 @@ export const Inbox = ({
|
||||||
i18n,
|
i18n,
|
||||||
isCustomizingPreferredReactions,
|
isCustomizingPreferredReactions,
|
||||||
renderCustomizingPreferredReactionsModal,
|
renderCustomizingPreferredReactionsModal,
|
||||||
|
renderLeftPane,
|
||||||
renderSafetyNumber,
|
renderSafetyNumber,
|
||||||
|
selectedConversationId,
|
||||||
|
selectedMessage,
|
||||||
|
showConversation,
|
||||||
|
showWhatsNewModal,
|
||||||
theme,
|
theme,
|
||||||
verifyConversationsStoppingSend,
|
verifyConversationsStoppingSend,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
const hostRef = useRef<HTMLDivElement | null>(null);
|
const [loadingMessageCount, setLoadingMessageCount] = useState(0);
|
||||||
const viewRef = useRef<InboxViewType | undefined>(undefined);
|
const [internalHasInitialLoadCompleted, setInternalHasInitialLoadCompleted] =
|
||||||
|
useState(hasInitialLoadCompleted);
|
||||||
|
|
||||||
|
const conversationMountRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const conversationViewRef = useRef<ConversationView | null>(null);
|
||||||
|
|
||||||
|
const [prevConversation, setPrevConversation] = useState<
|
||||||
|
ConversationModel | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const viewOptions: InboxViewOptionsType = {
|
if (!selectedConversationId) {
|
||||||
el: hostRef.current,
|
return;
|
||||||
initialLoadComplete: false,
|
}
|
||||||
window,
|
|
||||||
};
|
|
||||||
const view = new window.Whisper.InboxView(viewOptions);
|
|
||||||
|
|
||||||
viewRef.current = view;
|
const conversation = window.ConversationController.get(
|
||||||
|
selectedConversationId
|
||||||
|
);
|
||||||
|
strictAssert(conversation, 'Conversation must be found');
|
||||||
|
|
||||||
|
conversation.setMarkedUnread(false);
|
||||||
|
|
||||||
|
if (!prevConversation || prevConversation.id !== selectedConversationId) {
|
||||||
|
// We create a mount point because when calling .remove() on the Backbone
|
||||||
|
// view it'll also remove the mount point along with it.
|
||||||
|
const viewMountNode = document.createElement('div');
|
||||||
|
conversationMountRef.current?.appendChild(viewMountNode);
|
||||||
|
|
||||||
|
// Make sure to unload the previous conversation along with calling
|
||||||
|
// Backbone's remove so that it is taken out of the DOM.
|
||||||
|
if (prevConversation) {
|
||||||
|
prevConversation.trigger('unload', 'opened another conversation');
|
||||||
|
}
|
||||||
|
conversationViewRef.current?.remove();
|
||||||
|
|
||||||
|
// Can't import ConversationView directly because conversation_view
|
||||||
|
// needs access to window.Signal first.
|
||||||
|
const view = new window.Whisper.ConversationView({
|
||||||
|
el: viewMountNode,
|
||||||
|
model: conversation,
|
||||||
|
});
|
||||||
|
conversationViewRef.current = view;
|
||||||
|
|
||||||
|
setPrevConversation(conversation);
|
||||||
|
|
||||||
|
conversation.trigger('opened', selectedMessage);
|
||||||
|
} else if (selectedMessage) {
|
||||||
|
conversation.trigger('scroll-to-message', selectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure poppers are positioned properly
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
}, [prevConversation, selectedConversationId, selectedMessage]);
|
||||||
|
|
||||||
|
// Whenever the selectedConversationId is cleared we should also ensure
|
||||||
|
// that prevConversation is cleared too.
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevConversation && !selectedConversationId) {
|
||||||
|
setPrevConversation(undefined);
|
||||||
|
}
|
||||||
|
}, [prevConversation, selectedConversationId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function refreshConversation({
|
||||||
|
newId,
|
||||||
|
oldId,
|
||||||
|
}: {
|
||||||
|
newId: string;
|
||||||
|
oldId: string;
|
||||||
|
}) {
|
||||||
|
if (prevConversation && prevConversation.get('id') === oldId) {
|
||||||
|
showConversation({ conversationId: newId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close current opened conversation to reload the group information once
|
||||||
|
// linked.
|
||||||
|
function unload() {
|
||||||
|
if (!prevConversation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevConversation.trigger('unload', 'force unload requested');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShowConversation(id: string, messageId?: string): void {
|
||||||
|
showConversation({ conversationId: id, messageId });
|
||||||
|
}
|
||||||
|
|
||||||
|
function packInstallFailed() {
|
||||||
|
showToast(ToastStickerPackInstallFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Whisper.events.on('loadingProgress', setLoadingMessageCount);
|
||||||
|
window.Whisper.events.on('pack-install-failed', packInstallFailed);
|
||||||
|
window.Whisper.events.on('refreshConversation', refreshConversation);
|
||||||
|
window.Whisper.events.on('setupAsNewDevice', unload);
|
||||||
|
window.Whisper.events.on('showConversation', onShowConversation);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// [`Backbone.View.prototype.remove`][0] removes the DOM element and stops listening
|
window.Whisper.events.off('loadingProgress', setLoadingMessageCount);
|
||||||
// to event listeners. Because React will do the first, we only want to do the
|
window.Whisper.events.off('pack-install-failed', packInstallFailed);
|
||||||
// second.
|
window.Whisper.events.off('refreshConversation', refreshConversation);
|
||||||
// [0]: https://github.com/jashkenas/backbone/blob/153dc41616a1f2663e4a86b705fefd412ecb4a7a/backbone.js#L1336-L1342
|
window.Whisper.events.off('setupAsNewDevice', unload);
|
||||||
viewRef.current?.stopListening();
|
window.Whisper.events.off('showConversation', onShowConversation);
|
||||||
viewRef.current = undefined;
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, [prevConversation, showConversation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasInitialLoadCompleted && viewRef.current && viewRef.current.onEmpty) {
|
if (internalHasInitialLoadCompleted) {
|
||||||
viewRef.current.onEmpty();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const status = window.getSocketStatus();
|
||||||
|
switch (status) {
|
||||||
|
case 'CONNECTING':
|
||||||
|
break;
|
||||||
|
case 'OPEN':
|
||||||
|
// if we've connected, we can wait for real empty event
|
||||||
|
clearInterval(interval);
|
||||||
|
break;
|
||||||
|
case 'CLOSING':
|
||||||
|
case 'CLOSED':
|
||||||
|
clearInterval(interval);
|
||||||
|
// if we failed to connect, we pretend we loaded
|
||||||
|
setInternalHasInitialLoadCompleted(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.warn(
|
||||||
|
`startConnectionListener: Found unexpected socket status ${status}; setting load to done manually.`
|
||||||
|
);
|
||||||
|
setInternalHasInitialLoadCompleted(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, SECOND);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [internalHasInitialLoadCompleted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInternalHasInitialLoadCompleted(hasInitialLoadCompleted);
|
||||||
|
}, [hasInitialLoadCompleted]);
|
||||||
|
|
||||||
|
if (!internalHasInitialLoadCompleted) {
|
||||||
|
return (
|
||||||
|
<div className="app-loading-screen">
|
||||||
|
<div className="module-title-bar-drag-area" />
|
||||||
|
|
||||||
|
<div className="content">
|
||||||
|
<div className="module-splash-screen__logo module-img--150" />
|
||||||
|
<div className="container">
|
||||||
|
<span className="dot" />
|
||||||
|
<span className="dot" />
|
||||||
|
<span className="dot" />
|
||||||
|
</div>
|
||||||
|
<div className="message">
|
||||||
|
{loadingMessageCount
|
||||||
|
? i18n('loadingMessages', [String(loadingMessageCount)])
|
||||||
|
: i18n('loading')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [hasInitialLoadCompleted, viewRef]);
|
|
||||||
|
|
||||||
let activeModal: ReactNode;
|
let activeModal: ReactNode;
|
||||||
if (conversationsStoppingSend.length) {
|
if (conversationsStoppingSend.length) {
|
||||||
|
@ -94,7 +246,28 @@ export const Inbox = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="Inbox" ref={hostRef} />
|
<div className="Inbox">
|
||||||
|
<div className="module-title-bar-drag-area" />
|
||||||
|
|
||||||
|
<div className="left-pane-wrapper">{renderLeftPane()}</div>
|
||||||
|
|
||||||
|
<div className="conversation-stack">
|
||||||
|
<div id="toast" />
|
||||||
|
<div className="conversation" ref={conversationMountRef} />
|
||||||
|
{!prevConversation && (
|
||||||
|
<div className="no-conversation-open">
|
||||||
|
<div className="module-splash-screen__logo module-img--128 module-logo-blue" />
|
||||||
|
<h3>{i18n('welcomeToSignal')}</h3>
|
||||||
|
<p className="whats-new-placeholder">
|
||||||
|
<WhatsNewLink
|
||||||
|
i18n={i18n}
|
||||||
|
showWhatsNewModal={showWhatsNewModal}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{activeModal}
|
{activeModal}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -124,7 +124,6 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
||||||
getPreferredBadge: () => undefined,
|
getPreferredBadge: () => undefined,
|
||||||
i18n,
|
i18n,
|
||||||
preferredWidthFromStorage: 320,
|
preferredWidthFromStorage: 320,
|
||||||
openConversationInternal: action('openConversationInternal'),
|
|
||||||
regionCode: 'US',
|
regionCode: 'US',
|
||||||
challengeStatus: select(
|
challengeStatus: select(
|
||||||
'challengeStatus',
|
'challengeStatus',
|
||||||
|
@ -148,7 +147,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
||||||
getPreferredBadge={() => undefined}
|
getPreferredBadge={() => undefined}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id={id}
|
id={id}
|
||||||
openConversationInternal={action('openConversationInternal')}
|
showConversation={action('showConversation')}
|
||||||
sentAt={1587358800000}
|
sentAt={1587358800000}
|
||||||
snippet="Lorem <<left>>ipsum<<right>> wow"
|
snippet="Lorem <<left>>ipsum<<right>> wow"
|
||||||
theme={ThemeType.light}
|
theme={ThemeType.light}
|
||||||
|
|
|
@ -39,7 +39,7 @@ import {
|
||||||
getWidthFromPreferredWidth,
|
getWidthFromPreferredWidth,
|
||||||
} from '../util/leftPaneWidth';
|
} from '../util/leftPaneWidth';
|
||||||
import type { LookupConversationWithoutUuidActionsType } from '../util/lookupConversationWithoutUuid';
|
import type { LookupConversationWithoutUuidActionsType } from '../util/lookupConversationWithoutUuid';
|
||||||
import type { OpenConversationInternalType } from '../state/ducks/conversations';
|
import type { ShowConversationType } from '../state/ducks/conversations';
|
||||||
|
|
||||||
import { ConversationList } from './ConversationList';
|
import { ConversationList } from './ConversationList';
|
||||||
import { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox';
|
import { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox';
|
||||||
|
@ -99,25 +99,25 @@ export type PropsType = {
|
||||||
clearSearch: () => void;
|
clearSearch: () => void;
|
||||||
closeMaximumGroupSizeModal: () => void;
|
closeMaximumGroupSizeModal: () => void;
|
||||||
closeRecommendedGroupSizeModal: () => void;
|
closeRecommendedGroupSizeModal: () => void;
|
||||||
createGroup: () => void;
|
|
||||||
openConversationInternal: OpenConversationInternalType;
|
|
||||||
savePreferredLeftPaneWidth: (_: number) => void;
|
|
||||||
searchInConversation: (conversationId: string) => unknown;
|
|
||||||
setComposeSearchTerm: (composeSearchTerm: string) => void;
|
|
||||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => void;
|
|
||||||
setComposeGroupName: (_: string) => void;
|
|
||||||
setComposeGroupExpireTimer: (_: number) => void;
|
|
||||||
showArchivedConversations: () => void;
|
|
||||||
showInbox: () => void;
|
|
||||||
startComposing: () => void;
|
|
||||||
startSearch: () => unknown;
|
|
||||||
showChooseGroupMembers: () => void;
|
|
||||||
startSettingGroupMetadata: () => void;
|
|
||||||
toggleConversationInChooseMembers: (conversationId: string) => void;
|
|
||||||
composeDeleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
composeDeleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
||||||
composeReplaceAvatar: ReplaceAvatarActionType;
|
composeReplaceAvatar: ReplaceAvatarActionType;
|
||||||
composeSaveAvatarToDisk: SaveAvatarToDiskActionType;
|
composeSaveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||||
|
createGroup: () => void;
|
||||||
|
savePreferredLeftPaneWidth: (_: number) => void;
|
||||||
|
searchInConversation: (conversationId: string) => unknown;
|
||||||
|
setComposeGroupAvatar: (_: undefined | Uint8Array) => void;
|
||||||
|
setComposeGroupExpireTimer: (_: number) => void;
|
||||||
|
setComposeGroupName: (_: string) => void;
|
||||||
|
setComposeSearchTerm: (composeSearchTerm: string) => void;
|
||||||
|
showArchivedConversations: () => void;
|
||||||
|
showChooseGroupMembers: () => void;
|
||||||
|
showConversation: ShowConversationType;
|
||||||
|
showInbox: () => void;
|
||||||
|
startComposing: () => void;
|
||||||
|
startSearch: () => unknown;
|
||||||
|
startSettingGroupMetadata: () => void;
|
||||||
toggleComposeEditingAvatar: () => unknown;
|
toggleComposeEditingAvatar: () => unknown;
|
||||||
|
toggleConversationInChooseMembers: (conversationId: string) => void;
|
||||||
updateSearchTerm: (_: string) => void;
|
updateSearchTerm: (_: string) => void;
|
||||||
|
|
||||||
// Render Props
|
// Render Props
|
||||||
|
@ -137,8 +137,6 @@ export type PropsType = {
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
renderCaptchaDialog: (props: { onSkip(): void }) => JSX.Element;
|
renderCaptchaDialog: (props: { onSkip(): void }) => JSX.Element;
|
||||||
renderCrashReportDialog: () => JSX.Element;
|
renderCrashReportDialog: () => JSX.Element;
|
||||||
|
|
||||||
showConversation: (conversationId: string) => void;
|
|
||||||
} & LookupConversationWithoutUuidActionsType;
|
} & LookupConversationWithoutUuidActionsType;
|
||||||
|
|
||||||
export const LeftPane: React.FC<PropsType> = ({
|
export const LeftPane: React.FC<PropsType> = ({
|
||||||
|
@ -156,7 +154,6 @@ export const LeftPane: React.FC<PropsType> = ({
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
modeSpecificProps,
|
modeSpecificProps,
|
||||||
openConversationInternal,
|
|
||||||
preferredWidthFromStorage,
|
preferredWidthFromStorage,
|
||||||
renderCaptchaDialog,
|
renderCaptchaDialog,
|
||||||
renderCrashReportDialog,
|
renderCrashReportDialog,
|
||||||
|
@ -363,7 +360,7 @@ export const LeftPane: React.FC<PropsType> = ({
|
||||||
|
|
||||||
if (conversationToOpen) {
|
if (conversationToOpen) {
|
||||||
const { conversationId, messageId } = conversationToOpen;
|
const { conversationId, messageId } = conversationToOpen;
|
||||||
openConversationInternal({ conversationId, messageId });
|
showConversation({ conversationId, messageId });
|
||||||
if (openedByNumber) {
|
if (openedByNumber) {
|
||||||
clearSearch();
|
clearSearch();
|
||||||
}
|
}
|
||||||
|
@ -383,16 +380,16 @@ export const LeftPane: React.FC<PropsType> = ({
|
||||||
document.removeEventListener('keydown', onKeyDown);
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
|
clearSearch,
|
||||||
helper,
|
helper,
|
||||||
openConversationInternal,
|
|
||||||
searchInConversation,
|
searchInConversation,
|
||||||
selectedConversationId,
|
selectedConversationId,
|
||||||
selectedMessageId,
|
selectedMessageId,
|
||||||
showChooseGroupMembers,
|
showChooseGroupMembers,
|
||||||
|
showConversation,
|
||||||
showInbox,
|
showInbox,
|
||||||
startComposing,
|
startComposing,
|
||||||
startSearch,
|
startSearch,
|
||||||
clearSearch,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const requiresFullWidth = helper.requiresFullWidth();
|
const requiresFullWidth = helper.requiresFullWidth();
|
||||||
|
@ -488,13 +485,13 @@ export const LeftPane: React.FC<PropsType> = ({
|
||||||
|
|
||||||
const onSelectConversation = useCallback(
|
const onSelectConversation = useCallback(
|
||||||
(conversationId: string, messageId?: string) => {
|
(conversationId: string, messageId?: string) => {
|
||||||
openConversationInternal({
|
showConversation({
|
||||||
conversationId,
|
conversationId,
|
||||||
messageId,
|
messageId,
|
||||||
switchToAssociatedView: true,
|
switchToAssociatedView: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[openConversationInternal]
|
[showConversation]
|
||||||
);
|
);
|
||||||
|
|
||||||
const previousSelectedConversationId = usePrevious(
|
const previousSelectedConversationId = usePrevious(
|
||||||
|
@ -555,7 +552,7 @@ export const LeftPane: React.FC<PropsType> = ({
|
||||||
setComposeSearchTerm(event.target.value);
|
setComposeSearchTerm(event.target.value);
|
||||||
},
|
},
|
||||||
updateSearchTerm,
|
updateSearchTerm,
|
||||||
openConversationInternal,
|
showConversation,
|
||||||
})}
|
})}
|
||||||
<div className="module-left-pane__dialogs">
|
<div className="module-left-pane__dialogs">
|
||||||
{renderExpiredBuildDialog({
|
{renderExpiredBuildDialog({
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import type {
|
import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
OpenConversationInternalType,
|
ShowConversationType,
|
||||||
} from '../state/ducks/conversations';
|
} from '../state/ducks/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { Avatar, AvatarSize } from './Avatar';
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
|
@ -20,10 +20,10 @@ type PropsType = {
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
startSearchCounter: number;
|
startSearchCounter: number;
|
||||||
updateSearchTerm: (searchTerm: string) => void;
|
updateSearchTerm: (searchTerm: string) => void;
|
||||||
openConversationInternal: OpenConversationInternalType;
|
showConversation: ShowConversationType;
|
||||||
onEnterKeyDown?: (
|
onEnterKeyDown?: (
|
||||||
clearSearch: () => void,
|
clearSearch: () => void,
|
||||||
openConversationInternal: OpenConversationInternalType
|
showConversation: ShowConversationType
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export const LeftPaneSearchInput = ({
|
||||||
searchTerm,
|
searchTerm,
|
||||||
startSearchCounter,
|
startSearchCounter,
|
||||||
updateSearchTerm,
|
updateSearchTerm,
|
||||||
openConversationInternal,
|
showConversation,
|
||||||
onEnterKeyDown,
|
onEnterKeyDown,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
const inputRef = useRef<null | HTMLInputElement>(null);
|
const inputRef = useRef<null | HTMLInputElement>(null);
|
||||||
|
@ -103,7 +103,7 @@ export const LeftPaneSearchInput = ({
|
||||||
}}
|
}}
|
||||||
onKeyDown={event => {
|
onKeyDown={event => {
|
||||||
if (onEnterKeyDown && event.key === 'Enter') {
|
if (onEnterKeyDown && event.key === 'Enter') {
|
||||||
onEnterKeyDown(clearSearch, openConversationInternal);
|
onEnterKeyDown(clearSearch, showConversation);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,10 +81,10 @@ function getAttachmentWithThumbnail(url: string): AttachmentType {
|
||||||
const getDefaultProps = (): PropsType => ({
|
const getDefaultProps = (): PropsType => ({
|
||||||
hiddenStories: [],
|
hiddenStories: [],
|
||||||
i18n,
|
i18n,
|
||||||
openConversationInternal: action('openConversationInternal'),
|
|
||||||
preferredWidthFromStorage: 380,
|
preferredWidthFromStorage: 380,
|
||||||
queueStoryDownload: action('queueStoryDownload'),
|
queueStoryDownload: action('queueStoryDownload'),
|
||||||
renderStoryViewer: () => <div />,
|
renderStoryViewer: () => <div />,
|
||||||
|
showConversation: action('showConversation'),
|
||||||
stories: [
|
stories: [
|
||||||
createStory({
|
createStory({
|
||||||
attachment: getAttachmentWithThumbnail(
|
attachment: getAttachmentWithThumbnail(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import classNames from 'classnames';
|
||||||
import type { ConversationStoryType } from './StoryListItem';
|
import type { ConversationStoryType } from './StoryListItem';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { PropsType as SmartStoryViewerPropsType } from '../state/smart/StoryViewer';
|
import type { PropsType as SmartStoryViewerPropsType } from '../state/smart/StoryViewer';
|
||||||
|
import type { ShowConversationType } from '../state/ducks/conversations';
|
||||||
import { StoriesPane } from './StoriesPane';
|
import { StoriesPane } from './StoriesPane';
|
||||||
import { Theme, themeClassName } from '../util/theme';
|
import { Theme, themeClassName } from '../util/theme';
|
||||||
import { getWidthFromPreferredWidth } from '../util/leftPaneWidth';
|
import { getWidthFromPreferredWidth } from '../util/leftPaneWidth';
|
||||||
|
@ -16,9 +17,9 @@ export type PropsType = {
|
||||||
hiddenStories: Array<ConversationStoryType>;
|
hiddenStories: Array<ConversationStoryType>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
preferredWidthFromStorage: number;
|
preferredWidthFromStorage: number;
|
||||||
openConversationInternal: (_: { conversationId: string }) => unknown;
|
|
||||||
renderStoryViewer: (props: SmartStoryViewerPropsType) => JSX.Element;
|
|
||||||
queueStoryDownload: (storyId: string) => unknown;
|
queueStoryDownload: (storyId: string) => unknown;
|
||||||
|
renderStoryViewer: (props: SmartStoryViewerPropsType) => JSX.Element;
|
||||||
|
showConversation: ShowConversationType;
|
||||||
stories: Array<ConversationStoryType>;
|
stories: Array<ConversationStoryType>;
|
||||||
toggleHideStories: (conversationId: string) => unknown;
|
toggleHideStories: (conversationId: string) => unknown;
|
||||||
toggleStoriesView: () => unknown;
|
toggleStoriesView: () => unknown;
|
||||||
|
@ -27,10 +28,10 @@ export type PropsType = {
|
||||||
export const Stories = ({
|
export const Stories = ({
|
||||||
hiddenStories,
|
hiddenStories,
|
||||||
i18n,
|
i18n,
|
||||||
openConversationInternal,
|
|
||||||
preferredWidthFromStorage,
|
preferredWidthFromStorage,
|
||||||
queueStoryDownload,
|
queueStoryDownload,
|
||||||
renderStoryViewer,
|
renderStoryViewer,
|
||||||
|
showConversation,
|
||||||
stories,
|
stories,
|
||||||
toggleHideStories,
|
toggleHideStories,
|
||||||
toggleStoriesView,
|
toggleStoriesView,
|
||||||
|
@ -119,8 +120,8 @@ export const Stories = ({
|
||||||
});
|
});
|
||||||
setConversationIdToView(clickedIdToView);
|
setConversationIdToView(clickedIdToView);
|
||||||
}}
|
}}
|
||||||
openConversationInternal={openConversationInternal}
|
|
||||||
queueStoryDownload={queueStoryDownload}
|
queueStoryDownload={queueStoryDownload}
|
||||||
|
showConversation={showConversation}
|
||||||
stories={stories}
|
stories={stories}
|
||||||
toggleHideStories={toggleHideStories}
|
toggleHideStories={toggleHideStories}
|
||||||
toggleStoriesView={toggleStoriesView}
|
toggleStoriesView={toggleStoriesView}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import classNames from 'classnames';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import type { ConversationStoryType, StoryViewType } from './StoryListItem';
|
import type { ConversationStoryType, StoryViewType } from './StoryListItem';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
import type { ShowConversationType } from '../state/ducks/conversations';
|
||||||
import { SearchInput } from './SearchInput';
|
import { SearchInput } from './SearchInput';
|
||||||
import { StoryListItem } from './StoryListItem';
|
import { StoryListItem } from './StoryListItem';
|
||||||
|
|
||||||
|
@ -53,8 +54,8 @@ export type PropsType = {
|
||||||
hiddenStories: Array<ConversationStoryType>;
|
hiddenStories: Array<ConversationStoryType>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
onStoryClicked: (conversationId: string) => unknown;
|
onStoryClicked: (conversationId: string) => unknown;
|
||||||
openConversationInternal: (_: { conversationId: string }) => unknown;
|
|
||||||
queueStoryDownload: (storyId: string) => unknown;
|
queueStoryDownload: (storyId: string) => unknown;
|
||||||
|
showConversation: ShowConversationType;
|
||||||
stories: Array<ConversationStoryType>;
|
stories: Array<ConversationStoryType>;
|
||||||
toggleHideStories: (conversationId: string) => unknown;
|
toggleHideStories: (conversationId: string) => unknown;
|
||||||
toggleStoriesView: () => unknown;
|
toggleStoriesView: () => unknown;
|
||||||
|
@ -64,8 +65,8 @@ export const StoriesPane = ({
|
||||||
hiddenStories,
|
hiddenStories,
|
||||||
i18n,
|
i18n,
|
||||||
onStoryClicked,
|
onStoryClicked,
|
||||||
openConversationInternal,
|
|
||||||
queueStoryDownload,
|
queueStoryDownload,
|
||||||
|
showConversation,
|
||||||
stories,
|
stories,
|
||||||
toggleHideStories,
|
toggleHideStories,
|
||||||
toggleStoriesView,
|
toggleStoriesView,
|
||||||
|
@ -121,7 +122,7 @@ export const StoriesPane = ({
|
||||||
}}
|
}}
|
||||||
onHideStory={toggleHideStories}
|
onHideStory={toggleHideStories}
|
||||||
onGoToConversation={conversationId => {
|
onGoToConversation={conversationId => {
|
||||||
openConversationInternal({ conversationId });
|
showConversation({ conversationId });
|
||||||
toggleStoriesView();
|
toggleStoriesView();
|
||||||
}}
|
}}
|
||||||
queueStoryDownload={queueStoryDownload}
|
queueStoryDownload={queueStoryDownload}
|
||||||
|
@ -150,7 +151,7 @@ export const StoriesPane = ({
|
||||||
}}
|
}}
|
||||||
onHideStory={toggleHideStories}
|
onHideStory={toggleHideStories}
|
||||||
onGoToConversation={conversationId => {
|
onGoToConversation={conversationId => {
|
||||||
openConversationInternal({ conversationId });
|
showConversation({ conversationId });
|
||||||
toggleStoriesView();
|
toggleStoriesView();
|
||||||
}}
|
}}
|
||||||
queueStoryDownload={queueStoryDownload}
|
queueStoryDownload={queueStoryDownload}
|
||||||
|
|
|
@ -44,8 +44,8 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
i18n,
|
i18n,
|
||||||
isAdmin: boolean('isAdmin', overrideProps.isAdmin || false),
|
isAdmin: boolean('isAdmin', overrideProps.isAdmin || false),
|
||||||
isMember: boolean('isMember', overrideProps.isMember || true),
|
isMember: boolean('isMember', overrideProps.isMember || true),
|
||||||
openConversationInternal: action('openConversationInternal'),
|
|
||||||
removeMemberFromGroup: action('removeMemberFromGroup'),
|
removeMemberFromGroup: action('removeMemberFromGroup'),
|
||||||
|
showConversation: action('showConversation'),
|
||||||
theme: ThemeType.light,
|
theme: ThemeType.light,
|
||||||
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
||||||
toggleAdmin: action('toggleAdmin'),
|
toggleAdmin: action('toggleAdmin'),
|
||||||
|
|
|
@ -9,7 +9,10 @@ import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import { About } from './About';
|
import { About } from './About';
|
||||||
import { Avatar } from '../Avatar';
|
import { Avatar } from '../Avatar';
|
||||||
import { AvatarLightbox } from '../AvatarLightbox';
|
import { AvatarLightbox } from '../AvatarLightbox';
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type {
|
||||||
|
ConversationType,
|
||||||
|
ShowConversationType,
|
||||||
|
} from '../../state/ducks/conversations';
|
||||||
import { Modal } from '../Modal';
|
import { Modal } from '../Modal';
|
||||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||||
import { BadgeDialog } from '../BadgeDialog';
|
import { BadgeDialog } from '../BadgeDialog';
|
||||||
|
@ -32,14 +35,8 @@ export type PropsDataType = {
|
||||||
|
|
||||||
type PropsActionType = {
|
type PropsActionType = {
|
||||||
hideContactModal: () => void;
|
hideContactModal: () => void;
|
||||||
openConversationInternal: (
|
|
||||||
options: Readonly<{
|
|
||||||
conversationId: string;
|
|
||||||
messageId?: string;
|
|
||||||
switchToAssociatedView?: boolean;
|
|
||||||
}>
|
|
||||||
) => void;
|
|
||||||
removeMemberFromGroup: (conversationId: string, contactId: string) => void;
|
removeMemberFromGroup: (conversationId: string, contactId: string) => void;
|
||||||
|
showConversation: ShowConversationType;
|
||||||
toggleAdmin: (conversationId: string, contactId: string) => void;
|
toggleAdmin: (conversationId: string, contactId: string) => void;
|
||||||
toggleSafetyNumberModal: (conversationId: string) => unknown;
|
toggleSafetyNumberModal: (conversationId: string) => unknown;
|
||||||
updateConversationModelSharedGroups: (conversationId: string) => void;
|
updateConversationModelSharedGroups: (conversationId: string) => void;
|
||||||
|
@ -69,8 +66,8 @@ export const ContactModal = ({
|
||||||
i18n,
|
i18n,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isMember,
|
isMember,
|
||||||
openConversationInternal,
|
|
||||||
removeMemberFromGroup,
|
removeMemberFromGroup,
|
||||||
|
showConversation,
|
||||||
theme,
|
theme,
|
||||||
toggleAdmin,
|
toggleAdmin,
|
||||||
toggleSafetyNumberModal,
|
toggleSafetyNumberModal,
|
||||||
|
@ -205,7 +202,7 @@ export const ContactModal = ({
|
||||||
className="ContactModal__button ContactModal__send-message"
|
className="ContactModal__button ContactModal__send-message"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
hideContactModal();
|
hideContactModal();
|
||||||
openConversationInternal({ conversationId: contact.id });
|
showConversation({ conversationId: contact.id });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="ContactModal__bubble-icon">
|
<div className="ContactModal__bubble-icon">
|
||||||
|
|
|
@ -53,7 +53,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
to: overrideProps.to as PropsType['to'],
|
to: overrideProps.to as PropsType['to'],
|
||||||
getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined),
|
getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined),
|
||||||
isSelected: boolean('isSelected', overrideProps.isSelected || false),
|
isSelected: boolean('isSelected', overrideProps.isSelected || false),
|
||||||
openConversationInternal: action('openConversationInternal'),
|
showConversation: action('showConversation'),
|
||||||
isSearchingInConversation: boolean(
|
isSearchingInConversation: boolean(
|
||||||
'isSearchingInConversation',
|
'isSearchingInConversation',
|
||||||
overrideProps.isSearchingInConversation || false
|
overrideProps.isSearchingInConversation || false
|
||||||
|
|
|
@ -15,7 +15,10 @@ import type {
|
||||||
ThemeType,
|
ThemeType,
|
||||||
} from '../../types/Util';
|
} from '../../types/Util';
|
||||||
import { BaseConversationListItem } from './BaseConversationListItem';
|
import { BaseConversationListItem } from './BaseConversationListItem';
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type {
|
||||||
|
ConversationType,
|
||||||
|
ShowConversationType,
|
||||||
|
} from '../../state/ducks/conversations';
|
||||||
import type { PreferredBadgeSelectorType } from '../../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../../state/selectors/badges';
|
||||||
|
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
|
@ -58,10 +61,7 @@ export type PropsDataType = {
|
||||||
type PropsHousekeepingType = {
|
type PropsHousekeepingType = {
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
openConversationInternal: (_: {
|
showConversation: ShowConversationType;
|
||||||
conversationId: string;
|
|
||||||
messageId?: string;
|
|
||||||
}) => void;
|
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,15 +147,15 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
id,
|
id,
|
||||||
openConversationInternal,
|
|
||||||
sentAt,
|
sentAt,
|
||||||
|
showConversation,
|
||||||
snippet,
|
snippet,
|
||||||
theme,
|
theme,
|
||||||
to,
|
to,
|
||||||
}) {
|
}) {
|
||||||
const onClickItem = useCallback(() => {
|
const onClickItem = useCallback(() => {
|
||||||
openConversationInternal({ conversationId, messageId: id });
|
showConversation({ conversationId, messageId: id });
|
||||||
}, [openConversationInternal, conversationId, id]);
|
}, [showConversation, conversationId, id]);
|
||||||
|
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
return <div />;
|
return <div />;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { BaseConversationListItem } from './BaseConversationListItem';
|
||||||
import type { ParsedE164Type } from '../../util/libphonenumberInstance';
|
import type { ParsedE164Type } from '../../util/libphonenumberInstance';
|
||||||
import type { LookupConversationWithoutUuidActionsType } from '../../util/lookupConversationWithoutUuid';
|
import type { LookupConversationWithoutUuidActionsType } from '../../util/lookupConversationWithoutUuid';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
|
import type { ShowConversationType } from '../../state/ducks/conversations';
|
||||||
import { AvatarColors } from '../../types/Colors';
|
import { AvatarColors } from '../../types/Colors';
|
||||||
|
|
||||||
type PropsData = {
|
type PropsData = {
|
||||||
|
@ -20,7 +21,7 @@ type PropsData = {
|
||||||
|
|
||||||
type PropsHousekeeping = {
|
type PropsHousekeeping = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
showConversation: (conversationId: string) => void;
|
showConversation: ShowConversationType;
|
||||||
} & LookupConversationWithoutUuidActionsType;
|
} & LookupConversationWithoutUuidActionsType;
|
||||||
|
|
||||||
export type Props = PropsData & PropsHousekeeping;
|
export type Props = PropsData & PropsHousekeeping;
|
||||||
|
@ -55,7 +56,7 @@ export const StartNewConversation: FunctionComponent<Props> = React.memo(
|
||||||
});
|
});
|
||||||
|
|
||||||
if (conversationId !== undefined) {
|
if (conversationId !== undefined) {
|
||||||
showConversation(conversationId);
|
showConversation({ conversationId });
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
showConversation,
|
showConversation,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { BaseConversationListItem } from './BaseConversationListItem';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
import { lookupConversationWithoutUuid } from '../../util/lookupConversationWithoutUuid';
|
import { lookupConversationWithoutUuid } from '../../util/lookupConversationWithoutUuid';
|
||||||
import type { LookupConversationWithoutUuidActionsType } from '../../util/lookupConversationWithoutUuid';
|
import type { LookupConversationWithoutUuidActionsType } from '../../util/lookupConversationWithoutUuid';
|
||||||
|
import type { ShowConversationType } from '../../state/ducks/conversations';
|
||||||
|
|
||||||
type PropsData = {
|
type PropsData = {
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -17,7 +18,7 @@ type PropsData = {
|
||||||
|
|
||||||
type PropsHousekeeping = {
|
type PropsHousekeeping = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
showConversation: (conversationId: string) => void;
|
showConversation: ShowConversationType;
|
||||||
} & LookupConversationWithoutUuidActionsType;
|
} & LookupConversationWithoutUuidActionsType;
|
||||||
|
|
||||||
export type Props = PropsData & PropsHousekeeping;
|
export type Props = PropsData & PropsHousekeeping;
|
||||||
|
@ -44,7 +45,7 @@ export const UsernameSearchResultListItem: FunctionComponent<Props> = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (conversationId !== undefined) {
|
if (conversationId !== undefined) {
|
||||||
showConversation(conversationId);
|
showConversation({ conversationId });
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
username,
|
username,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import type { PropsData as ConversationListItemPropsType } from '../conversation
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
import type {
|
import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
OpenConversationInternalType,
|
ShowConversationType,
|
||||||
} from '../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
|
import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
|
||||||
import type { LeftPaneSearchPropsType } from './LeftPaneSearchHelper';
|
import type { LeftPaneSearchPropsType } from './LeftPaneSearchHelper';
|
||||||
|
@ -84,13 +84,13 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
||||||
clearSearch,
|
clearSearch,
|
||||||
i18n,
|
i18n,
|
||||||
updateSearchTerm,
|
updateSearchTerm,
|
||||||
openConversationInternal,
|
showConversation,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
clearConversationSearch: () => unknown;
|
clearConversationSearch: () => unknown;
|
||||||
clearSearch: () => unknown;
|
clearSearch: () => unknown;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
updateSearchTerm: (searchTerm: string) => unknown;
|
updateSearchTerm: (searchTerm: string) => unknown;
|
||||||
openConversationInternal: OpenConversationInternalType;
|
showConversation: ShowConversationType;
|
||||||
}>): ReactChild | null {
|
}>): ReactChild | null {
|
||||||
if (!this.searchConversation) {
|
if (!this.searchConversation) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -103,9 +103,9 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
searchConversation={this.searchConversation}
|
searchConversation={this.searchConversation}
|
||||||
searchTerm={this.searchTerm}
|
searchTerm={this.searchTerm}
|
||||||
|
showConversation={showConversation}
|
||||||
startSearchCounter={this.startSearchCounter}
|
startSearchCounter={this.startSearchCounter}
|
||||||
updateSearchTerm={updateSearchTerm}
|
updateSearchTerm={updateSearchTerm}
|
||||||
openConversationInternal={openConversationInternal}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type {
|
||||||
ReplaceAvatarActionType,
|
ReplaceAvatarActionType,
|
||||||
SaveAvatarToDiskActionType,
|
SaveAvatarToDiskActionType,
|
||||||
} from '../../types/Avatar';
|
} from '../../types/Avatar';
|
||||||
import type { OpenConversationInternalType } from '../../state/ducks/conversations';
|
import type { ShowConversationType } from '../../state/ducks/conversations';
|
||||||
|
|
||||||
export enum FindDirection {
|
export enum FindDirection {
|
||||||
Up,
|
Up,
|
||||||
|
@ -43,7 +43,7 @@ export abstract class LeftPaneHelper<T> {
|
||||||
event: ChangeEvent<HTMLInputElement>
|
event: ChangeEvent<HTMLInputElement>
|
||||||
) => unknown;
|
) => unknown;
|
||||||
updateSearchTerm: (searchTerm: string) => unknown;
|
updateSearchTerm: (searchTerm: string) => unknown;
|
||||||
openConversationInternal: OpenConversationInternalType;
|
showConversation: ShowConversationType;
|
||||||
}>
|
}>
|
||||||
): null | ReactChild {
|
): null | ReactChild {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Intl } from '../Intl';
|
||||||
import type { ToFindType } from './LeftPaneHelper';
|
import type { ToFindType } from './LeftPaneHelper';
|
||||||
import type {
|
import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
OpenConversationInternalType,
|
ShowConversationType,
|
||||||
} from '../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import { LeftPaneHelper } from './LeftPaneHelper';
|
import { LeftPaneHelper } from './LeftPaneHelper';
|
||||||
import { getConversationInDirection } from './getConversationInDirection';
|
import { getConversationInDirection } from './getConversationInDirection';
|
||||||
|
@ -85,14 +85,14 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
||||||
clearConversationSearch,
|
clearConversationSearch,
|
||||||
clearSearch,
|
clearSearch,
|
||||||
i18n,
|
i18n,
|
||||||
|
showConversation,
|
||||||
updateSearchTerm,
|
updateSearchTerm,
|
||||||
openConversationInternal,
|
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
clearConversationSearch: () => unknown;
|
clearConversationSearch: () => unknown;
|
||||||
clearSearch: () => unknown;
|
clearSearch: () => unknown;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
showConversation: ShowConversationType;
|
||||||
updateSearchTerm: (searchTerm: string) => unknown;
|
updateSearchTerm: (searchTerm: string) => unknown;
|
||||||
openConversationInternal: OpenConversationInternalType;
|
|
||||||
}>): ReactChild {
|
}>): ReactChild {
|
||||||
return (
|
return (
|
||||||
<LeftPaneSearchInput
|
<LeftPaneSearchInput
|
||||||
|
@ -102,9 +102,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
searchConversation={this.searchConversation}
|
searchConversation={this.searchConversation}
|
||||||
searchTerm={this.searchTerm}
|
searchTerm={this.searchTerm}
|
||||||
|
showConversation={showConversation}
|
||||||
startSearchCounter={this.startSearchCounter}
|
startSearchCounter={this.startSearchCounter}
|
||||||
updateSearchTerm={updateSearchTerm}
|
updateSearchTerm={updateSearchTerm}
|
||||||
openConversationInternal={openConversationInternal}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import type { PropsData as ConversationListItemPropsType } from '../conversation
|
||||||
import { handleKeydownForSearch } from './handleKeydownForSearch';
|
import { handleKeydownForSearch } from './handleKeydownForSearch';
|
||||||
import type {
|
import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
OpenConversationInternalType,
|
ShowConversationType,
|
||||||
} from '../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
|
import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
|
||||||
|
|
||||||
|
@ -104,14 +104,14 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
||||||
clearConversationSearch,
|
clearConversationSearch,
|
||||||
clearSearch,
|
clearSearch,
|
||||||
i18n,
|
i18n,
|
||||||
|
showConversation,
|
||||||
updateSearchTerm,
|
updateSearchTerm,
|
||||||
openConversationInternal,
|
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
clearConversationSearch: () => unknown;
|
clearConversationSearch: () => unknown;
|
||||||
clearSearch: () => unknown;
|
clearSearch: () => unknown;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
showConversation: ShowConversationType;
|
||||||
updateSearchTerm: (searchTerm: string) => unknown;
|
updateSearchTerm: (searchTerm: string) => unknown;
|
||||||
openConversationInternal: OpenConversationInternalType;
|
|
||||||
}>): ReactChild {
|
}>): ReactChild {
|
||||||
return (
|
return (
|
||||||
<LeftPaneSearchInput
|
<LeftPaneSearchInput
|
||||||
|
@ -119,12 +119,12 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
||||||
clearSearch={clearSearch}
|
clearSearch={clearSearch}
|
||||||
disabled={this.searchDisabled}
|
disabled={this.searchDisabled}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
onEnterKeyDown={this.onEnterKeyDown}
|
||||||
searchConversation={this.searchConversation}
|
searchConversation={this.searchConversation}
|
||||||
searchTerm={this.searchTerm}
|
searchTerm={this.searchTerm}
|
||||||
|
showConversation={showConversation}
|
||||||
startSearchCounter={this.startSearchCounter}
|
startSearchCounter={this.startSearchCounter}
|
||||||
updateSearchTerm={updateSearchTerm}
|
updateSearchTerm={updateSearchTerm}
|
||||||
openConversationInternal={openConversationInternal}
|
|
||||||
onEnterKeyDown={this.onEnterKeyDown}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -361,13 +361,13 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
||||||
|
|
||||||
private onEnterKeyDown(
|
private onEnterKeyDown(
|
||||||
clearSearch: () => unknown,
|
clearSearch: () => unknown,
|
||||||
openConversationInternal: OpenConversationInternalType
|
showConversation: ShowConversationType
|
||||||
): void {
|
): void {
|
||||||
const conversation = this.getConversationAndMessageAtIndex(0);
|
const conversation = this.getConversationAndMessageAtIndex(0);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openConversationInternal(conversation);
|
showConversation(conversation);
|
||||||
clearSearch();
|
clearSearch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
log.warn(
|
log.warn(
|
||||||
`joinViaLink/${logId}: Already a member of group, opening conversation`
|
`joinViaLink/${logId}: Already a member of group, opening conversation`
|
||||||
);
|
);
|
||||||
window.reduxActions.conversations.openConversationInternal({
|
window.reduxActions.conversations.showConversation({
|
||||||
conversationId: existingConversation.id,
|
conversationId: existingConversation.id,
|
||||||
});
|
});
|
||||||
showToast(ToastAlreadyGroupMember);
|
showToast(ToastAlreadyGroupMember);
|
||||||
|
@ -166,7 +166,7 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
// We're waiting for the left pane to re-sort before we navigate to that conversation
|
// We're waiting for the left pane to re-sort before we navigate to that conversation
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
|
|
||||||
window.reduxActions.conversations.openConversationInternal({
|
window.reduxActions.conversations.showConversation({
|
||||||
conversationId: existingConversation.id,
|
conversationId: existingConversation.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
log.warn(
|
log.warn(
|
||||||
`joinViaLink/${logId}: User is part of group on second check, opening conversation`
|
`joinViaLink/${logId}: User is part of group on second check, opening conversation`
|
||||||
);
|
);
|
||||||
window.reduxActions.conversations.openConversationInternal({
|
window.reduxActions.conversations.showConversation({
|
||||||
conversationId: targetConversation.id,
|
conversationId: targetConversation.id,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -347,7 +347,7 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.reduxActions.conversations.openConversationInternal({
|
window.reduxActions.conversations.showConversation({
|
||||||
conversationId: targetConversation.id,
|
conversationId: targetConversation.id,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
// The idea with this file is to make it webpackable for the style guide
|
// The idea with this file is to make it webpackable for the style guide
|
||||||
|
|
||||||
import * as Backbone from './backbone';
|
|
||||||
import * as Crypto from './Crypto';
|
import * as Crypto from './Crypto';
|
||||||
import * as Curve from './Curve';
|
import * as Curve from './Curve';
|
||||||
import { start as conversationControllerStart } from './ConversationController';
|
import { start as conversationControllerStart } from './ConversationController';
|
||||||
|
@ -33,7 +32,6 @@ import { createForwardMessageModal } from './state/roots/createForwardMessageMod
|
||||||
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
||||||
import { createGroupV1MigrationModal } from './state/roots/createGroupV1MigrationModal';
|
import { createGroupV1MigrationModal } from './state/roots/createGroupV1MigrationModal';
|
||||||
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
||||||
import { createLeftPane } from './state/roots/createLeftPane';
|
|
||||||
import { createMessageDetail } from './state/roots/createMessageDetail';
|
import { createMessageDetail } from './state/roots/createMessageDetail';
|
||||||
import { createConversationNotificationsSettings } from './state/roots/createConversationNotificationsSettings';
|
import { createConversationNotificationsSettings } from './state/roots/createConversationNotificationsSettings';
|
||||||
import { createGroupV2Permissions } from './state/roots/createGroupV2Permissions';
|
import { createGroupV2Permissions } from './state/roots/createGroupV2Permissions';
|
||||||
|
@ -424,7 +422,6 @@ export const setup = (options: {
|
||||||
createGroupV1MigrationModal,
|
createGroupV1MigrationModal,
|
||||||
createGroupV2JoinModal,
|
createGroupV2JoinModal,
|
||||||
createGroupV2Permissions,
|
createGroupV2Permissions,
|
||||||
createLeftPane,
|
|
||||||
createMessageDetail,
|
createMessageDetail,
|
||||||
createConversationNotificationsSettings,
|
createConversationNotificationsSettings,
|
||||||
createPendingInvites,
|
createPendingInvites,
|
||||||
|
@ -482,7 +479,6 @@ export const setup = (options: {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Backbone,
|
|
||||||
Components,
|
Components,
|
||||||
Crypto,
|
Crypto,
|
||||||
Curve,
|
Curve,
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
SwitchToAssociatedViewActionType,
|
|
||||||
MessageDeletedActionType,
|
MessageDeletedActionType,
|
||||||
MessageChangedActionType,
|
MessageChangedActionType,
|
||||||
|
SelectedConversationChangedActionType,
|
||||||
} from './conversations';
|
} from './conversations';
|
||||||
|
import { SELECTED_CONVERSATION_CHANGED } from './conversations';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -59,9 +60,9 @@ export function reducer(
|
||||||
state: Readonly<AudioPlayerStateType> = getEmptyState(),
|
state: Readonly<AudioPlayerStateType> = getEmptyState(),
|
||||||
action: Readonly<
|
action: Readonly<
|
||||||
| AudioPlayerActionType
|
| AudioPlayerActionType
|
||||||
| SwitchToAssociatedViewActionType
|
|
||||||
| MessageDeletedActionType
|
| MessageDeletedActionType
|
||||||
| MessageChangedActionType
|
| MessageChangedActionType
|
||||||
|
| SelectedConversationChangedActionType
|
||||||
>
|
>
|
||||||
): AudioPlayerStateType {
|
): AudioPlayerStateType {
|
||||||
if (action.type === 'audioPlayer/SET_ACTIVE_AUDIO_ID') {
|
if (action.type === 'audioPlayer/SET_ACTIVE_AUDIO_ID') {
|
||||||
|
@ -75,7 +76,7 @@ export function reducer(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset activeAudioID on conversation change.
|
// Reset activeAudioID on conversation change.
|
||||||
if (action.type === 'SWITCH_TO_ASSOCIATED_VIEW') {
|
if (action.type === SELECTED_CONVERSATION_CHANGED) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
activeAudioID: undefined,
|
activeAudioID: undefined,
|
||||||
|
|
|
@ -22,7 +22,6 @@ import { calling } from '../../services/calling';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
import { assert, strictAssert } from '../../util/assert';
|
import { assert, strictAssert } from '../../util/assert';
|
||||||
import * as universalExpireTimer from '../../util/universalExpireTimer';
|
import * as universalExpireTimer from '../../util/universalExpireTimer';
|
||||||
import { trigger } from '../../shims/events';
|
|
||||||
import type { ToggleProfileEditorErrorActionType } from './globalModals';
|
import type { ToggleProfileEditorErrorActionType } from './globalModals';
|
||||||
import { TOGGLE_PROFILE_EDITOR_ERROR } from './globalModals';
|
import { TOGGLE_PROFILE_EDITOR_ERROR } from './globalModals';
|
||||||
import { isRecord } from '../../util/isRecord';
|
import { isRecord } from '../../util/isRecord';
|
||||||
|
@ -347,12 +346,6 @@ export type ConversationsStateType = {
|
||||||
messagesByConversation: MessagesByConversationType;
|
messagesByConversation: MessagesByConversationType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenConversationInternalType = (_: {
|
|
||||||
conversationId: string;
|
|
||||||
messageId?: string;
|
|
||||||
switchToAssociatedView?: boolean;
|
|
||||||
}) => void;
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
export const getConversationCallMode = (
|
export const getConversationCallMode = (
|
||||||
|
@ -399,6 +392,8 @@ const CONVERSATION_STOPPED_BY_MISSING_VERIFICATION =
|
||||||
const DISCARD_MESSAGES = 'conversations/DISCARD_MESSAGES';
|
const DISCARD_MESSAGES = 'conversations/DISCARD_MESSAGES';
|
||||||
const REPLACE_AVATARS = 'conversations/REPLACE_AVATARS';
|
const REPLACE_AVATARS = 'conversations/REPLACE_AVATARS';
|
||||||
const UPDATE_USERNAME_SAVE_STATE = 'conversations/UPDATE_USERNAME_SAVE_STATE';
|
const UPDATE_USERNAME_SAVE_STATE = 'conversations/UPDATE_USERNAME_SAVE_STATE';
|
||||||
|
export const SELECTED_CONVERSATION_CHANGED =
|
||||||
|
'conversations/SELECTED_CONVERSATION_CHANGED';
|
||||||
|
|
||||||
export type CancelVerificationDataByConversationActionType = {
|
export type CancelVerificationDataByConversationActionType = {
|
||||||
type: typeof CANCEL_CONVERSATION_PENDING_VERIFICATION;
|
type: typeof CANCEL_CONVERSATION_PENDING_VERIFICATION;
|
||||||
|
@ -642,10 +637,11 @@ export type ClearUnreadMetricsActionType = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type SelectedConversationChangedActionType = {
|
export type SelectedConversationChangedActionType = {
|
||||||
type: 'SELECTED_CONVERSATION_CHANGED';
|
type: typeof SELECTED_CONVERSATION_CHANGED;
|
||||||
payload: {
|
payload: {
|
||||||
id: string;
|
id?: string;
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
|
switchToAssociatedView?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
type ReviewGroupMemberNameCollisionActionType = {
|
type ReviewGroupMemberNameCollisionActionType = {
|
||||||
|
@ -710,10 +706,6 @@ type ShowChooseGroupMembersActionType = {
|
||||||
type StartSettingGroupMetadataActionType = {
|
type StartSettingGroupMetadataActionType = {
|
||||||
type: 'START_SETTING_GROUP_METADATA';
|
type: 'START_SETTING_GROUP_METADATA';
|
||||||
};
|
};
|
||||||
export type SwitchToAssociatedViewActionType = {
|
|
||||||
type: 'SWITCH_TO_ASSOCIATED_VIEW';
|
|
||||||
payload: { conversationId: string };
|
|
||||||
};
|
|
||||||
export type ToggleConversationInChooseMembersActionType = {
|
export type ToggleConversationInChooseMembersActionType = {
|
||||||
type: 'TOGGLE_CONVERSATION_IN_CHOOSE_MEMBERS';
|
type: 'TOGGLE_CONVERSATION_IN_CHOOSE_MEMBERS';
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -792,7 +784,6 @@ export type ConversationActionType =
|
||||||
| ShowInboxActionType
|
| ShowInboxActionType
|
||||||
| StartComposingActionType
|
| StartComposingActionType
|
||||||
| StartSettingGroupMetadataActionType
|
| StartSettingGroupMetadataActionType
|
||||||
| SwitchToAssociatedViewActionType
|
|
||||||
| ToggleConversationInChooseMembersActionType
|
| ToggleConversationInChooseMembersActionType
|
||||||
| ToggleComposeEditingAvatarActionType
|
| ToggleComposeEditingAvatarActionType
|
||||||
| UpdateUsernameSaveStateActionType;
|
| UpdateUsernameSaveStateActionType;
|
||||||
|
@ -831,8 +822,6 @@ export const actions = {
|
||||||
messagesAdded,
|
messagesAdded,
|
||||||
messagesReset,
|
messagesReset,
|
||||||
myProfileChanged,
|
myProfileChanged,
|
||||||
openConversationExternal,
|
|
||||||
openConversationInternal,
|
|
||||||
removeAllConversations,
|
removeAllConversations,
|
||||||
removeCustomColorOnConversations,
|
removeCustomColorOnConversations,
|
||||||
removeMemberFromGroup,
|
removeMemberFromGroup,
|
||||||
|
@ -1527,9 +1516,9 @@ function createGroup(
|
||||||
| CreateGroupPendingActionType
|
| CreateGroupPendingActionType
|
||||||
| CreateGroupFulfilledActionType
|
| CreateGroupFulfilledActionType
|
||||||
| CreateGroupRejectedActionType
|
| CreateGroupRejectedActionType
|
||||||
| SwitchToAssociatedViewActionType
|
| SelectedConversationChangedActionType
|
||||||
> {
|
> {
|
||||||
return async (dispatch, getState, ...args) => {
|
return async (dispatch, getState) => {
|
||||||
const { composer } = getState().conversations;
|
const { composer } = getState().conversations;
|
||||||
if (
|
if (
|
||||||
composer?.step !== ComposerStep.SetGroupMetadata ||
|
composer?.step !== ComposerStep.SetGroupMetadata ||
|
||||||
|
@ -1559,10 +1548,12 @@ function createGroup(
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
openConversationInternal({
|
dispatch(
|
||||||
|
showConversation({
|
||||||
conversationId: conversation.id,
|
conversationId: conversation.id,
|
||||||
switchToAssociatedView: true,
|
switchToAssociatedView: true,
|
||||||
})(dispatch, getState, ...args);
|
})
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Failed to create group', err && err.stack ? err.stack : err);
|
log.error('Failed to create group', err && err.stack ? err.stack : err);
|
||||||
dispatch({ type: 'CREATE_GROUP_REJECTED' });
|
dispatch({ type: 'CREATE_GROUP_REJECTED' });
|
||||||
|
@ -1924,48 +1915,6 @@ function toggleConversationInChooseMembers(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we need two actions here to simplify. Operations outside of the left pane can
|
|
||||||
// trigger an 'openConversation' so we go through Whisper.events for all
|
|
||||||
// conversation selection. Internal just triggers the Whisper.event, and External
|
|
||||||
// makes the changes to the store.
|
|
||||||
function openConversationInternal({
|
|
||||||
conversationId,
|
|
||||||
messageId,
|
|
||||||
switchToAssociatedView,
|
|
||||||
}: Readonly<{
|
|
||||||
conversationId: string;
|
|
||||||
messageId?: string;
|
|
||||||
switchToAssociatedView?: boolean;
|
|
||||||
}>): ThunkAction<
|
|
||||||
void,
|
|
||||||
RootStateType,
|
|
||||||
unknown,
|
|
||||||
SwitchToAssociatedViewActionType
|
|
||||||
> {
|
|
||||||
return dispatch => {
|
|
||||||
trigger('showConversation', conversationId, messageId);
|
|
||||||
|
|
||||||
if (switchToAssociatedView) {
|
|
||||||
dispatch({
|
|
||||||
type: 'SWITCH_TO_ASSOCIATED_VIEW',
|
|
||||||
payload: { conversationId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function openConversationExternal(
|
|
||||||
id: string,
|
|
||||||
messageId?: string
|
|
||||||
): SelectedConversationChangedActionType {
|
|
||||||
return {
|
|
||||||
type: 'SELECTED_CONVERSATION_CHANGED',
|
|
||||||
payload: {
|
|
||||||
id,
|
|
||||||
messageId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleHideStories(
|
function toggleHideStories(
|
||||||
conversationId: string
|
conversationId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
|
@ -2039,12 +1988,26 @@ function showInbox(): ShowInboxActionType {
|
||||||
payload: null,
|
payload: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function showConversation(
|
|
||||||
conversationId: string
|
type ShowConversationArgsType = {
|
||||||
): ThunkAction<void, RootStateType, unknown, ShowInboxActionType> {
|
conversationId?: string;
|
||||||
return dispatch => {
|
messageId?: string;
|
||||||
trigger('showConversation', conversationId);
|
switchToAssociatedView?: boolean;
|
||||||
dispatch(showInbox());
|
};
|
||||||
|
export type ShowConversationType = (_: ShowConversationArgsType) => unknown;
|
||||||
|
|
||||||
|
function showConversation({
|
||||||
|
conversationId,
|
||||||
|
messageId,
|
||||||
|
switchToAssociatedView,
|
||||||
|
}: ShowConversationArgsType): SelectedConversationChangedActionType {
|
||||||
|
return {
|
||||||
|
type: SELECTED_CONVERSATION_CHANGED,
|
||||||
|
payload: {
|
||||||
|
id: conversationId,
|
||||||
|
messageId,
|
||||||
|
switchToAssociatedView,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function showArchivedConversations(): ShowArchivedConversationsActionType {
|
function showArchivedConversations(): ShowArchivedConversationsActionType {
|
||||||
|
@ -2475,7 +2438,7 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (action.type === 'CREATE_GROUP_FULFILLED') {
|
if (action.type === 'CREATE_GROUP_FULFILLED') {
|
||||||
// We don't do much here and instead rely on `openConversationInternal` to do most of
|
// We don't do much here and instead rely on `showConversation` to do most of
|
||||||
// the work.
|
// the work.
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -3065,14 +3028,31 @@ export function reducer(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
|
if (action.type === SELECTED_CONVERSATION_CHANGED) {
|
||||||
const { payload } = action;
|
const { payload } = action;
|
||||||
const { id } = payload;
|
const { id, messageId, switchToAssociatedView } = payload;
|
||||||
|
|
||||||
return {
|
const nextState = {
|
||||||
...omit(state, 'contactSpoofingReview'),
|
...omit(state, 'contactSpoofingReview'),
|
||||||
selectedConversationId: id,
|
selectedConversationId: id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (messageId) {
|
||||||
|
nextState.selectedMessage = messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (switchToAssociatedView && id) {
|
||||||
|
const conversation = getOwn(state.conversationLookup, id);
|
||||||
|
if (!conversation) {
|
||||||
|
return nextState;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...omit(nextState, 'composer'),
|
||||||
|
showArchived: Boolean(conversation.isArchived),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextState;
|
||||||
}
|
}
|
||||||
if (action.type === 'SHOW_INBOX') {
|
if (action.type === 'SHOW_INBOX') {
|
||||||
return {
|
return {
|
||||||
|
@ -3432,20 +3412,6 @@ export function reducer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === 'SWITCH_TO_ASSOCIATED_VIEW') {
|
|
||||||
const conversation = getOwn(
|
|
||||||
state.conversationLookup,
|
|
||||||
action.payload.conversationId
|
|
||||||
);
|
|
||||||
if (!conversation) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...omit(state, 'composer'),
|
|
||||||
showArchived: Boolean(conversation.isArchived),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === 'TOGGLE_CONVERSATION_IN_CHOOSE_MEMBERS') {
|
if (action.type === 'TOGGLE_CONVERSATION_IN_CHOOSE_MEMBERS') {
|
||||||
const { composer } = state;
|
const { composer } = state;
|
||||||
if (composer?.step !== ComposerStep.ChooseGroupMembers) {
|
if (composer?.step !== ComposerStep.ChooseGroupMembers) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
} from '../selectors/user';
|
} from '../selectors/user';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { SELECTED_CONVERSATION_CHANGED } from './conversations';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
searchMessages: dataSearchMessages,
|
searchMessages: dataSearchMessages,
|
||||||
|
@ -431,7 +432,7 @@ export function reducer(
|
||||||
return getEmptyState();
|
return getEmptyState();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
|
if (action.type === SELECTED_CONVERSATION_CHANGED) {
|
||||||
const { payload } = action;
|
const { payload } = action;
|
||||||
const { id, messageId } = payload;
|
const { id, messageId } = payload;
|
||||||
const { searchConversationId } = state;
|
const { searchConversationId } = state;
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
// Copyright 2019-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
|
|
||||||
import type { Store } from 'redux';
|
|
||||||
|
|
||||||
import { SmartLeftPane } from '../smart/LeftPane';
|
|
||||||
|
|
||||||
export const createLeftPane = (store: Store): React.ReactElement => (
|
|
||||||
<Provider store={store}>
|
|
||||||
<SmartLeftPane />
|
|
||||||
</Provider>
|
|
||||||
);
|
|
|
@ -10,6 +10,7 @@ import { App } from '../../components/App';
|
||||||
import { SmartCallManager } from './CallManager';
|
import { SmartCallManager } from './CallManager';
|
||||||
import { SmartCustomizingPreferredReactionsModal } from './CustomizingPreferredReactionsModal';
|
import { SmartCustomizingPreferredReactionsModal } from './CustomizingPreferredReactionsModal';
|
||||||
import { SmartGlobalModalContainer } from './GlobalModalContainer';
|
import { SmartGlobalModalContainer } from './GlobalModalContainer';
|
||||||
|
import { SmartLeftPane } from './LeftPane';
|
||||||
import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
|
import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
|
||||||
import { SmartStories } from './Stories';
|
import { SmartStories } from './Stories';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
|
@ -47,6 +48,7 @@ const mapStateToProps = (state: StateType) => {
|
||||||
<SmartCustomizingPreferredReactionsModal />
|
<SmartCustomizingPreferredReactionsModal />
|
||||||
),
|
),
|
||||||
renderGlobalModalContainer: () => <SmartGlobalModalContainer />,
|
renderGlobalModalContainer: () => <SmartGlobalModalContainer />,
|
||||||
|
renderLeftPane: () => <SmartLeftPane />,
|
||||||
renderSafetyNumber: (props: SafetyNumberProps) => (
|
renderSafetyNumber: (props: SafetyNumberProps) => (
|
||||||
<SmartSafetyNumberViewer {...props} />
|
<SmartSafetyNumberViewer {...props} />
|
||||||
),
|
),
|
||||||
|
@ -68,6 +70,8 @@ const mapStateToProps = (state: StateType) => {
|
||||||
registerSingleDevice: (number: string, code: string): Promise<void> => {
|
registerSingleDevice: (number: string, code: string): Promise<void> => {
|
||||||
return window.getAccountManager().registerSingleDevice(number, code);
|
return window.getAccountManager().registerSingleDevice(number, code);
|
||||||
},
|
},
|
||||||
|
selectedConversationId: state.conversations.selectedConversationId,
|
||||||
|
selectedMessage: state.conversations.selectedMessage,
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
|
|
||||||
executeMenuRole: (role: MenuItemConstructorOptions['role']): void => {
|
executeMenuRole: (role: MenuItemConstructorOptions['role']): void => {
|
||||||
|
|
|
@ -33,8 +33,7 @@ function renderStoryViewer({
|
||||||
|
|
||||||
export function SmartStories(): JSX.Element | null {
|
export function SmartStories(): JSX.Element | null {
|
||||||
const storiesActions = useStoriesActions();
|
const storiesActions = useStoriesActions();
|
||||||
const { openConversationInternal, toggleHideStories } =
|
const { showConversation, toggleHideStories } = useConversationsActions();
|
||||||
useConversationsActions();
|
|
||||||
|
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||||
|
|
||||||
|
@ -56,9 +55,9 @@ export function SmartStories(): JSX.Element | null {
|
||||||
<Stories
|
<Stories
|
||||||
hiddenStories={hiddenStories}
|
hiddenStories={hiddenStories}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
openConversationInternal={openConversationInternal}
|
|
||||||
preferredWidthFromStorage={preferredWidthFromStorage}
|
preferredWidthFromStorage={preferredWidthFromStorage}
|
||||||
renderStoryViewer={renderStoryViewer}
|
renderStoryViewer={renderStoryViewer}
|
||||||
|
showConversation={showConversation}
|
||||||
stories={stories}
|
stories={stories}
|
||||||
toggleHideStories={toggleHideStories}
|
toggleHideStories={toggleHideStories}
|
||||||
{...storiesActions}
|
{...storiesActions}
|
||||||
|
|
|
@ -41,8 +41,7 @@ export function SmartStoryViewer({
|
||||||
const storiesActions = useStoriesActions();
|
const storiesActions = useStoriesActions();
|
||||||
const { onSetSkinTone, toggleHasAllStoriesMuted } = useItemsActions();
|
const { onSetSkinTone, toggleHasAllStoriesMuted } = useItemsActions();
|
||||||
const { onUseEmoji } = useEmojisActions();
|
const { onUseEmoji } = useEmojisActions();
|
||||||
const { openConversationInternal, toggleHideStories } =
|
const { showConversation, toggleHideStories } = useConversationsActions();
|
||||||
useConversationsActions();
|
|
||||||
|
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
@ -74,7 +73,7 @@ export function SmartStoryViewer({
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onHideStory={toggleHideStories}
|
onHideStory={toggleHideStories}
|
||||||
onGoToConversation={senderId => {
|
onGoToConversation={senderId => {
|
||||||
openConversationInternal({ conversationId: senderId });
|
showConversation({ conversationId: senderId });
|
||||||
storiesActions.toggleStoriesView();
|
storiesActions.toggleStoriesView();
|
||||||
}}
|
}}
|
||||||
onNextUserStories={onNextUserStories}
|
onNextUserStories={onNextUserStories}
|
||||||
|
|
|
@ -4,8 +4,11 @@
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import { actions } from '../../../state/ducks/audioPlayer';
|
import { actions } from '../../../state/ducks/audioPlayer';
|
||||||
import type { SwitchToAssociatedViewActionType } from '../../../state/ducks/conversations';
|
import type { SelectedConversationChangedActionType } from '../../../state/ducks/conversations';
|
||||||
import { actions as conversationsActions } from '../../../state/ducks/conversations';
|
import {
|
||||||
|
SELECTED_CONVERSATION_CHANGED,
|
||||||
|
actions as conversationsActions,
|
||||||
|
} from '../../../state/ducks/conversations';
|
||||||
import { noopAction } from '../../../state/ducks/noop';
|
import { noopAction } from '../../../state/ducks/noop';
|
||||||
|
|
||||||
import type { StateType } from '../../../state/reducer';
|
import type { StateType } from '../../../state/reducer';
|
||||||
|
@ -51,9 +54,9 @@ describe('both/state/ducks/audioPlayer', () => {
|
||||||
it('resets activeAudioID when changing the conversation', () => {
|
it('resets activeAudioID when changing the conversation', () => {
|
||||||
const state = getInitializedState();
|
const state = getInitializedState();
|
||||||
|
|
||||||
const updated = rootReducer(state, <SwitchToAssociatedViewActionType>{
|
const updated = rootReducer(state, <SelectedConversationChangedActionType>{
|
||||||
type: 'SWITCH_TO_ASSOCIATED_VIEW',
|
type: SELECTED_CONVERSATION_CHANGED,
|
||||||
payload: { conversationId: 'any' },
|
payload: { id: 'any' },
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(updated.audioPlayer.activeAudioID, undefined);
|
assert.strictEqual(updated.audioPlayer.activeAudioID, undefined);
|
||||||
|
|
|
@ -15,13 +15,14 @@ import {
|
||||||
import type {
|
import type {
|
||||||
CancelVerificationDataByConversationActionType,
|
CancelVerificationDataByConversationActionType,
|
||||||
ConversationMessageType,
|
ConversationMessageType,
|
||||||
ConversationsStateType,
|
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
ConversationsStateType,
|
||||||
MessageType,
|
MessageType,
|
||||||
SwitchToAssociatedViewActionType,
|
SelectedConversationChangedActionType,
|
||||||
ToggleConversationInChooseMembersActionType,
|
ToggleConversationInChooseMembersActionType,
|
||||||
} from '../../../state/ducks/conversations';
|
} from '../../../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
|
SELECTED_CONVERSATION_CHANGED,
|
||||||
actions,
|
actions,
|
||||||
cancelConversationVerification,
|
cancelConversationVerification,
|
||||||
clearCancelledConversationVerification,
|
clearCancelledConversationVerification,
|
||||||
|
@ -56,7 +57,6 @@ const {
|
||||||
createGroup,
|
createGroup,
|
||||||
discardMessages,
|
discardMessages,
|
||||||
messageChanged,
|
messageChanged,
|
||||||
openConversationInternal,
|
|
||||||
repairNewestMessage,
|
repairNewestMessage,
|
||||||
repairOldestMessage,
|
repairOldestMessage,
|
||||||
resetAllChatColors,
|
resetAllChatColors,
|
||||||
|
@ -68,6 +68,7 @@ const {
|
||||||
setPreJoinConversation,
|
setPreJoinConversation,
|
||||||
showArchivedConversations,
|
showArchivedConversations,
|
||||||
showChooseGroupMembers,
|
showChooseGroupMembers,
|
||||||
|
showConversation,
|
||||||
showInbox,
|
showInbox,
|
||||||
startComposing,
|
startComposing,
|
||||||
startSettingGroupMetadata,
|
startSettingGroupMetadata,
|
||||||
|
@ -341,82 +342,40 @@ describe('both/state/ducks/conversations', () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('openConversationInternal', () => {
|
describe('showConversation', () => {
|
||||||
it("returns a thunk that triggers a 'showConversation' event when passed a conversation ID", () => {
|
it('selects a conversation id', () => {
|
||||||
const dispatch = sinon.spy();
|
const state = {
|
||||||
|
...getEmptyState(),
|
||||||
|
};
|
||||||
|
const action = showConversation({ conversationId: 'abc123' });
|
||||||
|
const nextState = reducer(state, action);
|
||||||
|
|
||||||
openConversationInternal({ conversationId: 'abc123' })(
|
assert.equal(nextState.selectedConversationId, 'abc123');
|
||||||
dispatch,
|
assert.isUndefined(nextState.selectedMessage);
|
||||||
getEmptyRootState,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(
|
|
||||||
window.Whisper.events.trigger as sinon.SinonSpy
|
|
||||||
);
|
|
||||||
sinon.assert.calledWith(
|
|
||||||
window.Whisper.events.trigger as sinon.SinonSpy,
|
|
||||||
'showConversation',
|
|
||||||
'abc123',
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a thunk that triggers a 'showConversation' event when passed a conversation ID and message ID", () => {
|
it('selects a conversation and a message', () => {
|
||||||
const dispatch = sinon.spy();
|
const state = {
|
||||||
|
...getEmptyState(),
|
||||||
openConversationInternal({
|
};
|
||||||
|
const action = showConversation({
|
||||||
conversationId: 'abc123',
|
conversationId: 'abc123',
|
||||||
messageId: 'xyz987',
|
messageId: 'xyz987',
|
||||||
})(dispatch, getEmptyRootState, null);
|
});
|
||||||
|
const nextState = reducer(state, action);
|
||||||
|
|
||||||
sinon.assert.calledOnce(
|
assert.equal(nextState.selectedConversationId, 'abc123');
|
||||||
window.Whisper.events.trigger as sinon.SinonSpy
|
assert.equal(nextState.selectedMessage, 'xyz987');
|
||||||
);
|
|
||||||
sinon.assert.calledWith(
|
|
||||||
window.Whisper.events.trigger as sinon.SinonSpy,
|
|
||||||
'showConversation',
|
|
||||||
'abc123',
|
|
||||||
'xyz987'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a thunk that doesn't dispatch any actions by default", () => {
|
describe('showConversation switchToAssociatedView=true', () => {
|
||||||
const dispatch = sinon.spy();
|
let action: SelectedConversationChangedActionType;
|
||||||
|
|
||||||
openConversationInternal({ conversationId: 'abc123' })(
|
|
||||||
dispatch,
|
|
||||||
getEmptyRootState,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
sinon.assert.notCalled(dispatch);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('dispatches a SWITCH_TO_ASSOCIATED_VIEW action if called with a flag', () => {
|
|
||||||
const dispatch = sinon.spy();
|
|
||||||
|
|
||||||
openConversationInternal({
|
|
||||||
conversationId: 'abc123',
|
|
||||||
switchToAssociatedView: true,
|
|
||||||
})(dispatch, getEmptyRootState, null);
|
|
||||||
|
|
||||||
sinon.assert.calledWith(dispatch, {
|
|
||||||
type: 'SWITCH_TO_ASSOCIATED_VIEW',
|
|
||||||
payload: { conversationId: 'abc123' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('SWITCH_TO_ASSOCIATED_VIEW', () => {
|
|
||||||
let action: SwitchToAssociatedViewActionType;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const dispatch = sinon.spy();
|
action = showConversation({
|
||||||
openConversationInternal({
|
|
||||||
conversationId: 'fake-conversation-id',
|
conversationId: 'fake-conversation-id',
|
||||||
switchToAssociatedView: true,
|
switchToAssociatedView: true,
|
||||||
})(dispatch, getEmptyRootState, null);
|
});
|
||||||
[action] = dispatch.getCall(0).args;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the inbox if the conversation is not archived', () => {
|
it('shows the inbox if the conversation is not archived', () => {
|
||||||
|
@ -451,13 +410,6 @@ describe('both/state/ducks/conversations', () => {
|
||||||
assert.isUndefined(result.composer);
|
assert.isUndefined(result.composer);
|
||||||
assert.isTrue(result.showArchived);
|
assert.isTrue(result.showArchived);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does nothing if the conversation is not found', () => {
|
|
||||||
const state = getEmptyState();
|
|
||||||
const result = reducer(state, action);
|
|
||||||
|
|
||||||
assert.strictEqual(result, state);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -769,26 +721,23 @@ describe('both/state/ducks/conversations', () => {
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
sinon.assert.calledWith(
|
|
||||||
window.Whisper.events.trigger as sinon.SinonSpy,
|
|
||||||
'showConversation',
|
|
||||||
'9876',
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
sinon.assert.calledWith(dispatch, {
|
sinon.assert.calledWith(dispatch, {
|
||||||
type: 'CREATE_GROUP_FULFILLED',
|
type: 'CREATE_GROUP_FULFILLED',
|
||||||
payload: { invitedUuids: [abc] },
|
payload: { invitedUuids: [abc] },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledWith(dispatch, {
|
||||||
|
type: SELECTED_CONVERSATION_CHANGED,
|
||||||
|
payload: {
|
||||||
|
id: '9876',
|
||||||
|
messageId: undefined,
|
||||||
|
switchToAssociatedView: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const fulfilledAction = dispatch.getCall(1).args[0];
|
const fulfilledAction = dispatch.getCall(1).args[0];
|
||||||
const result = reducer(conversationsState, fulfilledAction);
|
const result = reducer(conversationsState, fulfilledAction);
|
||||||
assert.deepEqual(result.invitedUuidsForNewlyCreatedGroup, [abc]);
|
assert.deepEqual(result.invitedUuidsForNewlyCreatedGroup, [abc]);
|
||||||
|
|
||||||
sinon.assert.calledWith(dispatch, {
|
|
||||||
type: 'SWITCH_TO_ASSOCIATED_VIEW',
|
|
||||||
payload: { conversationId: '9876' },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8300,22 +8300,6 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-07-21T18:34:59.251Z"
|
"updated": "2020-07-21T18:34:59.251Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"rule": "DOM-innerHTML",
|
|
||||||
"path": "ts/backbone/views/Lightbox.ts",
|
|
||||||
"line": " container.innerHTML = '';",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-17T20:50:40.689Z",
|
|
||||||
"reasonDetail": "Hard-coded value"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "DOM-innerHTML",
|
|
||||||
"path": "ts/backbone/views/Lightbox.ts",
|
|
||||||
"line": " container.innerHTML = '';",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-17T20:50:40.689Z",
|
|
||||||
"reasonDetail": "Hard-coded value"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"rule": "jQuery-html(",
|
"rule": "jQuery-html(",
|
||||||
"path": "ts/backbone/views/whisper_view.ts",
|
"path": "ts/backbone/views/whisper_view.ts",
|
||||||
|
@ -8598,16 +8582,16 @@
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/Inbox.tsx",
|
"path": "ts/components/Inbox.tsx",
|
||||||
"line": " const hostRef = useRef<HTMLDivElement | null>(null);",
|
"line": " const conversationMountRef = useRef<HTMLDivElement | null>(null);",
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-07-30T16:57:33.618Z"
|
"updated": "2022-06-15T01:24:12.761Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/Inbox.tsx",
|
"path": "ts/components/Inbox.tsx",
|
||||||
"line": " const viewRef = useRef<InboxViewType | undefined>(undefined);",
|
"line": " const conversationViewRef = useRef<ConversationView | null>(null);",
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-07-30T16:57:33.618Z"
|
"updated": "2022-06-15T01:24:12.761Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
|
@ -9121,90 +9105,6 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2021-09-17T21:51:57.475Z"
|
"updated": "2021-09-17T21:51:57.475Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " template: () => $('#app-loading-screen').html(),",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " this.$('.message').text(message);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " template: () => $('#two-column').html(),",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " el: this.$('.conversation-stack'),",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " this.$('.no-conversation-open').toggle(!isAnyConversationOpen);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-10-08T17:40:22.770Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " this.$('.left-pane-placeholder').replaceWith(this.leftPaneView.el);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-10-08T17:40:22.770Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-10-22T20:58:48.103Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-append(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-10-22T20:58:48.103Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-appendTo(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " view.$el.appendTo(this.el);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-html(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " template: () => $('#app-loading-screen').html(),",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-html(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " template: () => $('#two-column').html(),",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-prependTo(",
|
|
||||||
"path": "ts/views/inbox_view.tsx",
|
|
||||||
"line": " this.appLoadingScreen.$el.prependTo(this.el);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2021-09-15T21:07:50.995Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "ts/windows/loading/start.ts",
|
"path": "ts/windows/loading/start.ts",
|
||||||
|
|
39
ts/util/showLightbox.tsx
Normal file
39
ts/util/showLightbox.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import type { PropsType } from '../components/Lightbox';
|
||||||
|
import { Lightbox } from '../components/Lightbox';
|
||||||
|
|
||||||
|
// NOTE: This file is temporarily here for convenicence of use by
|
||||||
|
// conversation_view while it is transitioning from Backbone into pure React.
|
||||||
|
// Please use <Lightbox /> directly and DO NOT USE THESE FUNCTIONS.
|
||||||
|
|
||||||
|
let lightboxMountNode: HTMLElement | undefined;
|
||||||
|
|
||||||
|
export function isLightboxOpen(): boolean {
|
||||||
|
return Boolean(lightboxMountNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeLightbox(): void {
|
||||||
|
if (!lightboxMountNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.ReactDOM.unmountComponentAtNode(lightboxMountNode);
|
||||||
|
document.body.removeChild(lightboxMountNode);
|
||||||
|
lightboxMountNode = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showLightbox(props: PropsType): void {
|
||||||
|
if (lightboxMountNode) {
|
||||||
|
closeLightbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
lightboxMountNode = document.createElement('div');
|
||||||
|
lightboxMountNode.setAttribute('data-id', 'lightbox');
|
||||||
|
document.body.appendChild(lightboxMountNode);
|
||||||
|
|
||||||
|
render(<Lightbox {...props} />, lightboxMountNode);
|
||||||
|
}
|
|
@ -58,7 +58,7 @@ import {
|
||||||
import { getActiveCallState } from '../state/selectors/calling';
|
import { getActiveCallState } from '../state/selectors/calling';
|
||||||
import { getTheme } from '../state/selectors/user';
|
import { getTheme } from '../state/selectors/user';
|
||||||
import { ReactWrapperView } from './ReactWrapperView';
|
import { ReactWrapperView } from './ReactWrapperView';
|
||||||
import { Lightbox } from '../components/Lightbox';
|
import type { Lightbox } from '../components/Lightbox';
|
||||||
import { ConversationDetailsMembershipList } from '../components/conversation/conversation-details/ConversationDetailsMembershipList';
|
import { ConversationDetailsMembershipList } from '../components/conversation/conversation-details/ConversationDetailsMembershipList';
|
||||||
import { showSafetyNumberChangeDialog } from '../shims/showSafetyNumberChangeDialog';
|
import { showSafetyNumberChangeDialog } from '../shims/showSafetyNumberChangeDialog';
|
||||||
import type {
|
import type {
|
||||||
|
@ -121,6 +121,11 @@ import { retryDeleteForEveryone } from '../util/retryDeleteForEveryone';
|
||||||
import { ContactDetail } from '../components/conversation/ContactDetail';
|
import { ContactDetail } from '../components/conversation/ContactDetail';
|
||||||
import { MediaGallery } from '../components/conversation/media-gallery/MediaGallery';
|
import { MediaGallery } from '../components/conversation/media-gallery/MediaGallery';
|
||||||
import type { ItemClickEvent } from '../components/conversation/media-gallery/types/ItemClickEvent';
|
import type { ItemClickEvent } from '../components/conversation/media-gallery/types/ItemClickEvent';
|
||||||
|
import {
|
||||||
|
closeLightbox,
|
||||||
|
isLightboxOpen,
|
||||||
|
showLightbox,
|
||||||
|
} from '../util/showLightbox';
|
||||||
|
|
||||||
type AttachmentOptions = {
|
type AttachmentOptions = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
|
@ -1905,28 +1910,23 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
await message.markViewOnceMessageViewed();
|
await message.markViewOnceMessageViewed();
|
||||||
|
|
||||||
const closeLightbox = async () => {
|
this.listenTo(message, 'expired', async () => {
|
||||||
log.info('displayTapToViewMessage: attempting to close lightbox');
|
log.info('displayTapToViewMessage: attempting to close lightbox');
|
||||||
|
|
||||||
if (!this.lightboxView) {
|
// This isn't really a bullet-proof check because the lightbox could
|
||||||
|
// be open while we're viewing a regular media message
|
||||||
|
if (!isLightboxOpen()) {
|
||||||
log.info('displayTapToViewMessage: lightbox was already closed');
|
log.info('displayTapToViewMessage: lightbox was already closed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lightboxView } = this;
|
|
||||||
this.lightboxView = undefined;
|
|
||||||
|
|
||||||
this.stopListening(message);
|
this.stopListening(message);
|
||||||
window.Signal.Backbone.Views.Lightbox.hide();
|
closeLightbox();
|
||||||
lightboxView.remove();
|
|
||||||
|
|
||||||
await deleteTempFile(tempPath);
|
await deleteTempFile(tempPath);
|
||||||
};
|
});
|
||||||
this.listenTo(message, 'expired', closeLightbox);
|
|
||||||
this.listenTo(message, 'change', () => {
|
this.listenTo(message, 'change', () => {
|
||||||
if (this.lightboxView) {
|
showLightbox(getProps());
|
||||||
this.lightboxView.update(<Lightbox {...getProps()} />);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getProps = (): ComponentProps<typeof Lightbox> => {
|
const getProps = (): ComponentProps<typeof Lightbox> => {
|
||||||
|
@ -1934,7 +1934,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
close: () => {
|
close: () => {
|
||||||
this.lightboxView?.remove();
|
closeLightbox();
|
||||||
},
|
},
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
media: [
|
media: [
|
||||||
|
@ -1957,18 +1957,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.lightboxView) {
|
showLightbox(getProps());
|
||||||
this.lightboxView.remove();
|
|
||||||
this.lightboxView = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lightboxView = new ReactWrapperView({
|
|
||||||
className: 'lightbox-wrapper',
|
|
||||||
JSX: <Lightbox {...getProps()} />,
|
|
||||||
onClose: closeLightbox,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.Signal.Backbone.Views.Lightbox.show(this.lightboxView.el);
|
|
||||||
|
|
||||||
log.info('displayTapToViewMessage: showed lightbox');
|
log.info('displayTapToViewMessage: showed lightbox');
|
||||||
}
|
}
|
||||||
|
@ -2084,34 +2073,17 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
mediaItem.attachment.path === selectedMediaItem.attachment.path
|
mediaItem.attachment.path === selectedMediaItem.attachment.path
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.lightboxView) {
|
showLightbox({
|
||||||
this.lightboxView.remove();
|
close: closeLightbox,
|
||||||
this.lightboxView = undefined;
|
i18n: window.i18n,
|
||||||
}
|
getConversation: getConversationSelector(window.reduxStore.getState()),
|
||||||
|
media,
|
||||||
this.lightboxView = new ReactWrapperView({
|
onForward: messageId => {
|
||||||
className: 'lightbox-wrapper',
|
|
||||||
JSX: (
|
|
||||||
<Lightbox
|
|
||||||
close={() => {
|
|
||||||
this.lightboxView?.remove();
|
|
||||||
}}
|
|
||||||
i18n={window.i18n}
|
|
||||||
getConversation={getConversationSelector(
|
|
||||||
window.reduxStore.getState()
|
|
||||||
)}
|
|
||||||
media={media}
|
|
||||||
onForward={messageId => {
|
|
||||||
this.showForwardMessageModal(messageId);
|
this.showForwardMessageModal(messageId);
|
||||||
}}
|
},
|
||||||
onSave={onSave}
|
onSave,
|
||||||
selectedIndex={selectedIndex >= 0 ? selectedIndex : 0}
|
selectedIndex: selectedIndex >= 0 ? selectedIndex : 0,
|
||||||
/>
|
|
||||||
),
|
|
||||||
onClose: () => window.Signal.Backbone.Views.Lightbox.hide(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.Signal.Backbone.Views.Lightbox.show(this.lightboxView.el);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showLightbox({
|
showLightbox({
|
||||||
|
|
|
@ -1,231 +0,0 @@
|
||||||
// Copyright 2014-2022 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import * as Backbone from 'backbone';
|
|
||||||
import * as log from '../logging/log';
|
|
||||||
import type { ConversationModel } from '../models/conversations';
|
|
||||||
import { ReactWrapperView } from './ReactWrapperView';
|
|
||||||
import { showToast } from '../util/showToast';
|
|
||||||
import { strictAssert } from '../util/assert';
|
|
||||||
import { WhatsNewLink } from '../components/WhatsNewLink';
|
|
||||||
import { ToastStickerPackInstallFailed } from '../components/ToastStickerPackInstallFailed';
|
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
|
||||||
const { Whisper } = window;
|
|
||||||
|
|
||||||
class ConversationStack extends Backbone.View {
|
|
||||||
public override className = 'conversation-stack';
|
|
||||||
|
|
||||||
private conversationStack: Array<ConversationModel> = [];
|
|
||||||
|
|
||||||
private getTopConversation(): undefined | ConversationModel {
|
|
||||||
return this.conversationStack[this.conversationStack.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public open(conversation: ConversationModel, messageId: string): void {
|
|
||||||
const topConversation = this.getTopConversation();
|
|
||||||
|
|
||||||
if (!topConversation || topConversation.id !== conversation.id) {
|
|
||||||
const view = new Whisper.ConversationView({
|
|
||||||
model: conversation,
|
|
||||||
});
|
|
||||||
this.listenTo(conversation, 'unload', () => this.onUnload(conversation));
|
|
||||||
this.listenTo(conversation, 'showSafetyNumber', () =>
|
|
||||||
view.showSafetyNumber()
|
|
||||||
);
|
|
||||||
view.$el.appendTo(this.el);
|
|
||||||
|
|
||||||
if (topConversation) {
|
|
||||||
topConversation.trigger('unload', 'opened another conversation');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conversationStack.push(conversation);
|
|
||||||
|
|
||||||
conversation.trigger('opened', messageId);
|
|
||||||
} else if (messageId) {
|
|
||||||
conversation.trigger('scroll-to-message', messageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
public unload(): void {
|
|
||||||
this.getTopConversation()?.trigger('unload', 'force unload requested');
|
|
||||||
}
|
|
||||||
|
|
||||||
private onUnload(conversation: ConversationModel) {
|
|
||||||
this.stopListening(conversation);
|
|
||||||
this.conversationStack = this.conversationStack.filter(
|
|
||||||
(c: ConversationModel) => c !== conversation
|
|
||||||
);
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override render(): ConversationStack {
|
|
||||||
const isAnyConversationOpen = Boolean(this.conversationStack.length);
|
|
||||||
this.$('.no-conversation-open').toggle(!isAnyConversationOpen);
|
|
||||||
|
|
||||||
// Make sure poppers are positioned properly
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppLoadingScreen = Whisper.View.extend({
|
|
||||||
template: () => $('#app-loading-screen').html(),
|
|
||||||
className: 'app-loading-screen',
|
|
||||||
updateProgress(count: number) {
|
|
||||||
if (count > 0) {
|
|
||||||
const message = window.i18n('loadingMessages', [count.toString()]);
|
|
||||||
this.$('.message').text(message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render_attributes: {
|
|
||||||
message: window.i18n('loading'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Whisper.InboxView = Whisper.View.extend({
|
|
||||||
template: () => $('#two-column').html(),
|
|
||||||
className: 'Inbox',
|
|
||||||
initialize(
|
|
||||||
options: {
|
|
||||||
initialLoadComplete?: boolean;
|
|
||||||
window?: typeof window;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
this.ready = false;
|
|
||||||
this.render();
|
|
||||||
|
|
||||||
this.conversation_stack = new ConversationStack({
|
|
||||||
el: this.$('.conversation-stack'),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.renderWhatsNew();
|
|
||||||
|
|
||||||
Whisper.events.on('refreshConversation', ({ oldId, newId }) => {
|
|
||||||
const convo = this.conversation_stack.lastConversation;
|
|
||||||
if (convo && convo.get('id') === oldId) {
|
|
||||||
this.conversation_stack.open(newId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close current opened conversation to reload the group information once
|
|
||||||
// linked.
|
|
||||||
Whisper.events.on('setupAsNewDevice', () => {
|
|
||||||
this.conversation_stack.unload();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.Whisper.events.on('showConversation', (id, messageId) => {
|
|
||||||
const conversation = window.ConversationController.get(id);
|
|
||||||
strictAssert(conversation, 'Conversation must be found');
|
|
||||||
|
|
||||||
conversation.setMarkedUnread(false);
|
|
||||||
|
|
||||||
const { openConversationExternal } = window.reduxActions.conversations;
|
|
||||||
if (openConversationExternal) {
|
|
||||||
openConversationExternal(conversation.id, messageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conversation_stack.open(conversation, messageId);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.Whisper.events.on('loadingProgress', count => {
|
|
||||||
const view = this.appLoadingScreen;
|
|
||||||
if (view) {
|
|
||||||
view.updateProgress(count);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!options.initialLoadComplete) {
|
|
||||||
this.appLoadingScreen = new AppLoadingScreen();
|
|
||||||
this.appLoadingScreen.render();
|
|
||||||
this.appLoadingScreen.$el.prependTo(this.el);
|
|
||||||
this.startConnectionListener();
|
|
||||||
} else {
|
|
||||||
this.setupLeftPane();
|
|
||||||
}
|
|
||||||
|
|
||||||
Whisper.events.on('pack-install-failed', () => {
|
|
||||||
showToast(ToastStickerPackInstallFailed);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render_attributes: {
|
|
||||||
welcomeToSignal: window.i18n('welcomeToSignal'),
|
|
||||||
// TODO DESKTOP-1451: add back the selectAContact message
|
|
||||||
selectAContact: '',
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
click: 'onClick',
|
|
||||||
},
|
|
||||||
renderWhatsNew() {
|
|
||||||
if (this.whatsNewLink) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { showWhatsNewModal } = window.reduxActions.globalModals;
|
|
||||||
this.whatsNewLink = new ReactWrapperView({
|
|
||||||
JSX: (
|
|
||||||
<WhatsNewLink
|
|
||||||
i18n={window.i18n}
|
|
||||||
showWhatsNewModal={showWhatsNewModal}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
this.$('.whats-new-placeholder').append(this.whatsNewLink.el);
|
|
||||||
},
|
|
||||||
setupLeftPane() {
|
|
||||||
if (this.leftPaneView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.leftPaneView = new ReactWrapperView({
|
|
||||||
className: 'left-pane-wrapper',
|
|
||||||
JSX: window.Signal.State.Roots.createLeftPane(window.reduxStore),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$('.left-pane-placeholder').replaceWith(this.leftPaneView.el);
|
|
||||||
},
|
|
||||||
startConnectionListener() {
|
|
||||||
this.interval = setInterval(() => {
|
|
||||||
const status = window.getSocketStatus();
|
|
||||||
switch (status) {
|
|
||||||
case 'CONNECTING':
|
|
||||||
break;
|
|
||||||
case 'OPEN':
|
|
||||||
clearInterval(this.interval);
|
|
||||||
// if we've connected, we can wait for real empty event
|
|
||||||
this.interval = null;
|
|
||||||
break;
|
|
||||||
case 'CLOSING':
|
|
||||||
case 'CLOSED':
|
|
||||||
clearInterval(this.interval);
|
|
||||||
this.interval = null;
|
|
||||||
// if we failed to connect, we pretend we got an empty event
|
|
||||||
this.onEmpty();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
log.warn(
|
|
||||||
`startConnectionListener: Found unexpected socket status ${status}; calling onEmpty() manually.`
|
|
||||||
);
|
|
||||||
this.onEmpty();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
onEmpty() {
|
|
||||||
this.setupLeftPane();
|
|
||||||
|
|
||||||
const view = this.appLoadingScreen;
|
|
||||||
if (view) {
|
|
||||||
this.appLoadingScreen = null;
|
|
||||||
view.remove();
|
|
||||||
|
|
||||||
const searchInput = document.querySelector(
|
|
||||||
'.LeftPaneSearchInput__input'
|
|
||||||
) as HTMLElement;
|
|
||||||
searchInput?.focus?.();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
3
ts/window.d.ts
vendored
3
ts/window.d.ts
vendored
|
@ -44,7 +44,6 @@ import { createGroupLinkManagement } from './state/roots/createGroupLinkManageme
|
||||||
import { createGroupV1MigrationModal } from './state/roots/createGroupV1MigrationModal';
|
import { createGroupV1MigrationModal } from './state/roots/createGroupV1MigrationModal';
|
||||||
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
||||||
import { createGroupV2Permissions } from './state/roots/createGroupV2Permissions';
|
import { createGroupV2Permissions } from './state/roots/createGroupV2Permissions';
|
||||||
import { createLeftPane } from './state/roots/createLeftPane';
|
|
||||||
import { createMessageDetail } from './state/roots/createMessageDetail';
|
import { createMessageDetail } from './state/roots/createMessageDetail';
|
||||||
import { createConversationNotificationsSettings } from './state/roots/createConversationNotificationsSettings';
|
import { createConversationNotificationsSettings } from './state/roots/createConversationNotificationsSettings';
|
||||||
import { createPendingInvites } from './state/roots/createPendingInvites';
|
import { createPendingInvites } from './state/roots/createPendingInvites';
|
||||||
|
@ -137,7 +136,6 @@ export declare class WebAudioRecorderClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SignalCoreType = {
|
export type SignalCoreType = {
|
||||||
Backbone: any;
|
|
||||||
Crypto: typeof Crypto;
|
Crypto: typeof Crypto;
|
||||||
Curve: typeof Curve;
|
Curve: typeof Curve;
|
||||||
Data: typeof Data;
|
Data: typeof Data;
|
||||||
|
@ -187,7 +185,6 @@ export type SignalCoreType = {
|
||||||
createGroupV1MigrationModal: typeof createGroupV1MigrationModal;
|
createGroupV1MigrationModal: typeof createGroupV1MigrationModal;
|
||||||
createGroupV2JoinModal: typeof createGroupV2JoinModal;
|
createGroupV2JoinModal: typeof createGroupV2JoinModal;
|
||||||
createGroupV2Permissions: typeof createGroupV2Permissions;
|
createGroupV2Permissions: typeof createGroupV2Permissions;
|
||||||
createLeftPane: typeof createLeftPane;
|
|
||||||
createMessageDetail: typeof createMessageDetail;
|
createMessageDetail: typeof createMessageDetail;
|
||||||
createConversationNotificationsSettings: typeof createConversationNotificationsSettings;
|
createConversationNotificationsSettings: typeof createConversationNotificationsSettings;
|
||||||
createPendingInvites: typeof createPendingInvites;
|
createPendingInvites: typeof createPendingInvites;
|
||||||
|
|
|
@ -8,6 +8,5 @@ import '../../models/conversations';
|
||||||
|
|
||||||
import '../../backbone/views/whisper_view';
|
import '../../backbone/views/whisper_view';
|
||||||
import '../../views/conversation_view';
|
import '../../views/conversation_view';
|
||||||
import '../../views/inbox_view';
|
|
||||||
import '../../SignalProtocolStore';
|
import '../../SignalProtocolStore';
|
||||||
import '../../background';
|
import '../../background';
|
||||||
|
|
Loading…
Reference in a new issue