Removes Backbone.View and jQuery

This commit is contained in:
Josh Perez 2023-01-02 16:34:41 -05:00 committed by GitHub
parent 26d689982a
commit 5e6eeecede
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1113 additions and 7637 deletions

View file

@ -1349,29 +1349,6 @@ Signal Desktop makes use of the following open source projects.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## jquery
Copyright JS Foundation and other contributors, https://js.foundation/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## js-yaml
(The MIT License)
@ -1767,20 +1744,6 @@ Signal Desktop makes use of the following open source projects.
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## mustache
The MIT License
Copyright (c) 2009 Chris Wanstrath (Ruby)
Copyright (c) 2010-2014 Jan Lehnardt (JavaScript)
Copyright (c) 2010-2015 The mustache.js community
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## node-fetch
License: MIT

View file

@ -94,40 +94,6 @@
type="text/css"
/>
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<!--
When making changes to these templates, be sure to update test/index.html as well
-->
<script type="text/x-tmpl-mustache" id="app-loading-screen">
<div class='module-title-bar-drag-area'></div>
<div class='content'>
<div class="module-splash-screen__logo module-img--150"></div>
<div class='container'>
<span class='dot'></span>
<span class='dot'></span>
<span class='dot'></span>
</div>
<div class='message'>{{ message }}</div>
</div>
</script>
<script type="text/x-tmpl-mustache" id="conversation">
<div class="ConversationView__template"></div>
</script>
<script type="text/x-tmpl-mustache" id="recorder">
<button class='close' tabIndex='2'><span class='icon'></span></button>
<span class='time'>0:00</span>
<button class='finish' tabIndex='1'><span class='icon'></span></button>
</script>
<script type="text/x-tmpl-mustache" id="group-member-list">
<div class='container' tabindex='0'>
{{ #summary }} <div class='summary'>{{ summary }}</div>{{ /summary }}
</div>
</script>
</head>
<body class="overflow-hidden">
<div id="app-container">
@ -158,7 +124,6 @@
type="text/javascript"
src="ts/manage_full_screen_class.js"
></script>
<script type="text/javascript" src="ts/backbone/backboneJquery.js"></script>
<script
type="text/javascript"
src="ts/backbone/reliable_trigger.js"

View file

@ -122,7 +122,6 @@
"history": "4.9.0",
"humanize-duration": "3.27.1",
"intl-tel-input": "17.0.13",
"jquery": "3.5.0",
"js-yaml": "3.13.1",
"linkify-it": "2.2.0",
"lodash": "4.17.21",
@ -132,7 +131,6 @@
"memoizee": "0.4.14",
"moment": "2.29.4",
"mp4box": "0.5.2",
"mustache": "2.3.0",
"node-fetch": "2.6.7",
"normalize-path": "3.0.0",
"p-map": "2.1.0",
@ -220,7 +218,6 @@
"@types/history": "4.7.2",
"@types/humanize-duration": "3.18.1",
"@types/intl-tel-input": "17.0.4",
"@types/jquery": "3.5.6",
"@types/js-yaml": "3.12.0",
"@types/json-to-ast": "2.1.2",
"@types/linkify-it": "2.1.0",

File diff suppressed because one or more lines are too long

View file

@ -15,9 +15,5 @@
<div id="root"></div>
<script type="text/javascript" src="../../js/components.js"></script>
<script type="text/javascript" src="../../ts/set_os_class.js"></script>
<script
type="text/javascript"
src="../../ts/backbone/backbonejQuery.js"
></script>
</body>
</html>

View file

@ -12,46 +12,7 @@
<div id="mocha"></div>
<div id="tests"></div>
<script type="text/x-tmpl-mustache" id="app-loading-screen">
<div class='module-title-bar-drag-area'></div>
<div class='content'>
<div class="module-splash-screen__logo module-img--150"></div>
<div class='container'>
<span class='dot'></span>
<span class='dot'></span>
<span class='dot'></span>
</div>
<div class='message'>{{ message }}</div>
</div>
</script>
<script type="text/x-tmpl-mustache" id="conversation">
<div id="ConversationView__template"></div>
</script>
<script type="text/x-tmpl-mustache" id="recorder">
<button class='finish'><span class='icon'></span></button>
<span class='time'>0:00</span>
<button class='close'><span class='icon'></span></button>
</script>
<script type="text/x-tmpl-mustache" id="file-size-modal">
{{ file-size-warning }}
({{ limit }}{{ units }})
</script>
<script type="text/x-tmpl-mustache" id="group-member-list">
<div class='container'>
{{ #summary }} <div class='summary'>{{ summary }}</div>{{ /summary }}
</div>
</script>
<script type="text/javascript" src="../js/components.js"></script>
<script
type="text/javascript"
src="../ts/backbone/backboneJquery.js"
></script>
<script
type="text/javascript"
src="../ts/backbone/reliable_trigger.js"

View file

@ -1,6 +0,0 @@
// Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// we are requiring backbone in preload.js, and we need to tell backbone where
// jquery is after it's loaded.
window.Backbone.$ = window.Backbone.$ || window.$;

View file

@ -149,7 +149,6 @@ function trigger<
[
window.Backbone.Model.prototype,
window.Backbone.View.prototype,
window.Backbone.Collection.prototype,
window.Backbone.Events,
].forEach(proto => {

View file

@ -1155,12 +1155,13 @@ export async function startApp(): Promise<void> {
conversationChanged,
conversationRemoved,
removeAllConversations,
onConversationClosed,
} = window.reduxActions.conversations;
convoCollection.on('remove', conversation => {
const { id } = conversation || {};
conversation.trigger('unload', 'removed');
onConversationClosed(id, 'removed');
conversationRemoved(id);
});
convoCollection.on('add', conversation => {
@ -1580,7 +1581,7 @@ export async function startApp(): Promise<void> {
event.preventDefault();
event.stopPropagation();
conversation.trigger('unload', 'keyboard shortcut close');
onConversationClosed(conversation.id, 'keyboard shortcut close');
window.reduxActions.conversations.showConversation({
conversationId: undefined,
messageId: undefined,

View file

@ -1,7 +1,6 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ComponentProps } from 'react';
import React, { useEffect } from 'react';
import { Globals } from '@react-spring/web';
import classNames from 'classnames';
@ -10,10 +9,9 @@ import type { ExecuteMenuRoleType } from './TitleBarContainer';
import type { MenuOptionsType, MenuActionType } from '../types/menu';
import type { ToastType } from '../types/Toast';
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
import type { ReplacementValuesType } from '../types/Util';
import type { LocalizerType, ReplacementValuesType } from '../types/Util';
import { ThemeType } from '../types/Util';
import { AppViewType } from '../state/ducks/app';
import { Inbox } from './Inbox';
import { SmartInstallScreen } from '../state/smart/InstallScreen';
import { StandaloneRegistration } from './StandaloneRegistration';
import { TitleBarContainer } from './TitleBarContainer';
@ -28,6 +26,7 @@ type PropsType = {
renderCallManager: () => JSX.Element;
renderGlobalModalContainer: () => JSX.Element;
isShowingStoriesView: boolean;
i18n: LocalizerType;
renderStories: (closeView: () => unknown) => JSX.Element;
hasSelectedStoryData: boolean;
renderStoryViewer: (closeView: () => unknown) => JSX.Element;
@ -57,41 +56,33 @@ type PropsType = {
scrollToMessage: (conversationId: string, messageId: string) => unknown;
toggleStoriesView: () => unknown;
viewStory: ViewStoryActionCreatorType;
} & ComponentProps<typeof Inbox>;
renderInbox: () => JSX.Element;
};
export function App({
appView,
executeMenuAction,
executeMenuRole,
hasInitialLoadCompleted,
hasCustomTitleBar,
hasSelectedStoryData,
hideMenuBar,
hideToast,
i18n,
isCustomizingPreferredReactions,
isFullScreen,
isMaximized,
isShowingStoriesView,
hasCustomTitleBar,
menuOptions,
onUndoArchive,
openInbox,
openFileInFolder,
openInbox,
registerSingleDevice,
renderCallManager,
renderCustomizingPreferredReactionsModal,
renderGlobalModalContainer,
renderLeftPane,
renderInbox,
renderLightbox,
renderStories,
renderStoryViewer,
requestVerification,
scrollToMessage,
selectedConversationId,
selectedMessage,
selectedMessageSource,
showConversation,
showWhatsNewModal,
theme,
titleBarDoubleClick,
toast,
@ -115,23 +106,7 @@ export function App({
/>
);
} else if (appView === AppViewType.Inbox) {
contents = (
<Inbox
hasInitialLoadCompleted={hasInitialLoadCompleted}
i18n={i18n}
isCustomizingPreferredReactions={isCustomizingPreferredReactions}
renderCustomizingPreferredReactionsModal={
renderCustomizingPreferredReactionsModal
}
renderLeftPane={renderLeftPane}
scrollToMessage={scrollToMessage}
selectedConversationId={selectedConversationId}
selectedMessage={selectedMessage}
selectedMessageSource={selectedMessageSource}
showConversation={showConversation}
showWhatsNewModal={showWhatsNewModal}
/>
);
contents = renderInbox();
}
// This are here so that themes are properly applied to anything that is

View file

@ -1,38 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useEffect, useRef } from 'react';
import type * as Backbone from 'backbone';
type PropsType = {
View: typeof Backbone.View;
className?: string;
};
export function BackboneHost({ View, className }: PropsType): JSX.Element {
const hostRef = useRef<HTMLDivElement | null>(null);
const viewRef = useRef<Backbone.View | undefined>(undefined);
useEffect(() => {
const view = new View({
el: hostRef.current,
});
viewRef.current = view;
return () => {
if (!viewRef || !viewRef.current) {
return;
}
viewRef.current.remove();
viewRef.current = undefined;
};
}, [View]);
return (
<div>
<div className={className} ref={hostRef} />
</div>
);
}

View file

@ -2,11 +2,9 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useState } from 'react';
import type { ConversationModel } from '../models/conversations';
import type { ShowConversationType } from '../state/ducks/conversations';
import type { ConversationView } from '../views/conversation_view';
import type { LocalizerType } from '../types/Util';
import * as log from '../logging/log';
@ -16,11 +14,15 @@ import { WhatsNewLink } from './WhatsNewLink';
import { showToast } from '../util/showToast';
import { strictAssert } from '../util/assert';
import { SelectedMessageSource } from '../state/ducks/conversationsEnums';
import { usePrevious } from '../hooks/usePrevious';
export type PropsType = {
hasInitialLoadCompleted: boolean;
i18n: LocalizerType;
isCustomizingPreferredReactions: boolean;
onConversationClosed: (id: string, reason: string) => unknown;
onConversationOpened: (id: string, messageId?: string) => unknown;
renderConversationView: () => JSX.Element;
renderCustomizingPreferredReactionsModal: () => JSX.Element;
renderLeftPane: () => JSX.Element;
scrollToMessage: (conversationId: string, messageId: string) => unknown;
@ -35,6 +37,9 @@ export function Inbox({
hasInitialLoadCompleted,
i18n,
isCustomizingPreferredReactions,
onConversationClosed,
onConversationOpened,
renderConversationView,
renderCustomizingPreferredReactionsModal,
renderLeftPane,
scrollToMessage,
@ -48,14 +53,28 @@ export function Inbox({
const [internalHasInitialLoadCompleted, setInternalHasInitialLoadCompleted] =
useState(hasInitialLoadCompleted);
const conversationMountRef = useRef<HTMLDivElement | null>(null);
const conversationViewRef = useRef<ConversationView | null>(null);
const [prevConversation, setPrevConversation] = useState<
ConversationModel | undefined
>();
const prevConversationId = usePrevious(
selectedConversationId,
selectedConversationId
);
useEffect(() => {
if (prevConversationId !== selectedConversationId) {
if (prevConversationId) {
onConversationClosed(prevConversationId, 'opened another conversation');
}
if (selectedConversationId) {
onConversationOpened(selectedConversationId, selectedMessage);
}
} else if (
selectedConversationId &&
selectedMessage &&
selectedMessageSource !== SelectedMessageSource.Focus
) {
scrollToMessage(selectedConversationId, selectedMessage);
}
if (!selectedConversationId) {
return;
}
@ -66,53 +85,16 @@ export function Inbox({
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 &&
selectedMessageSource !== SelectedMessageSource.Focus
) {
scrollToMessage(conversation.id, selectedMessage);
}
}, [
prevConversation,
onConversationClosed,
onConversationOpened,
prevConversationId,
scrollToMessage,
selectedConversationId,
selectedMessage,
selectedMessageSource,
]);
// 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,
@ -121,7 +103,7 @@ export function Inbox({
newId: string;
oldId: string;
}) {
if (prevConversation && prevConversation.get('id') === oldId) {
if (prevConversationId === oldId) {
showConversation({ conversationId: newId });
}
}
@ -129,10 +111,10 @@ export function Inbox({
// Close current opened conversation to reload the group information once
// linked.
function unload() {
if (!prevConversation) {
if (!prevConversationId) {
return;
}
prevConversation.trigger('unload', 'force unload requested');
onConversationClosed(prevConversationId, 'force unload requested');
}
function packInstallFailed() {
@ -150,7 +132,7 @@ export function Inbox({
window.Whisper.events.off('refreshConversation', refreshConversation);
window.Whisper.events.off('setupAsNewDevice', unload);
};
}, [prevConversation, showConversation]);
}, [onConversationClosed, prevConversationId, showConversation]);
useEffect(() => {
if (internalHasInitialLoadCompleted) {
@ -226,8 +208,15 @@ export function Inbox({
<div className="conversation-stack">
<div id="toast" />
<div className="conversation" ref={conversationMountRef} />
{!prevConversation && (
{selectedConversationId && (
<div
className="conversation"
id={`conversation-${selectedConversationId}`}
>
{renderConversationView()}
</div>
)}
{!prevConversationId && (
<div className="no-conversation-open">
<div className="module-splash-screen__logo module-img--128 module-logo-blue" />
<h3>{i18n('welcomeToSignal')}</h3>

View file

@ -236,9 +236,9 @@ export class ConversationModel extends window.Backbone
throttledBumpTyping?: () => void;
throttledFetchSMSOnlyUUID?: () => Promise<void> | void;
throttledFetchSMSOnlyUUID?: () => Promise<void> | undefined;
throttledMaybeMigrateV1Group?: () => Promise<void> | void;
throttledMaybeMigrateV1Group?: () => Promise<void> | undefined;
throttledGetProfiles?: () => Promise<void>;
@ -2286,7 +2286,10 @@ export class ConversationModel extends window.Backbone
void this.updateLastMessage();
if (isLocalAction) {
this.trigger('unload', 'deleted from message request');
window.reduxActions.conversations.onConversationClosed(
this.id,
'deleted from message request'
);
if (isGroupV1(this.attributes)) {
await this.leaveGroup();
@ -2305,7 +2308,10 @@ export class ConversationModel extends window.Backbone
void this.updateLastMessage();
if (isLocalAction) {
this.trigger('unload', 'blocked and deleted from message request');
window.reduxActions.conversations.onConversationClosed(
this.id,
'blocked and deleted from message request'
);
if (isGroupV1(this.attributes)) {
await this.leaveGroup();

View file

@ -9,11 +9,8 @@ import { copyFileSync, readFileSync, writeFileSync } from 'fs';
console.log('Concatenating...');
const BASE_BOWER = join(__dirname, '../../components');
const BASE_NODE = join(__dirname, '../../node_modules');
const CONCAT_TARGET = join(__dirname, '../../js/components.js');
const CONCAT_SOURCES = [
join(BASE_NODE, 'jquery/dist/jquery.js'),
join(BASE_NODE, 'mustache/mustache.js'),
join(BASE_BOWER, 'webaudiorecorder/lib/WebAudioRecorder.js'),
];

View file

@ -103,12 +103,12 @@ type AddPendingAttachmentActionType = {
payload: AttachmentDraftType;
};
type ReplaceAttachmentsActionType = {
export type ReplaceAttachmentsActionType = {
type: typeof REPLACE_ATTACHMENTS;
payload: ReadonlyArray<AttachmentDraftType>;
};
type ResetComposerActionType = {
export type ResetComposerActionType = {
type: typeof RESET_COMPOSER;
};
@ -117,7 +117,7 @@ type SetComposerDisabledStateActionType = {
payload: boolean;
};
type SetFocusActionType = {
export type SetFocusActionType = {
type: typeof SET_FOCUS;
};
@ -126,7 +126,7 @@ type SetHighQualitySettingActionType = {
payload: boolean;
};
type SetQuotedMessageActionType = {
export type SetQuotedMessageActionType = {
type: typeof SET_QUOTED_MESSAGE;
payload?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
};
@ -473,7 +473,7 @@ function getAttachmentsFromConversationModel(
return conversation?.get('draftAttachments') || [];
}
function setQuoteByMessageId(
export function setQuoteByMessageId(
conversationId: string,
messageId: string | undefined
): ThunkAction<
@ -627,7 +627,7 @@ function addPendingAttachment(
};
}
function setComposerFocus(
export function setComposerFocus(
conversationId: string
): ThunkAction<void, RootStateType, unknown, SetFocusActionType> {
return async (dispatch, getState) => {
@ -894,7 +894,7 @@ function removeAttachment(
};
}
function replaceAttachments(
export function replaceAttachments(
conversationId: string,
attachments: ReadonlyArray<AttachmentDraftType>
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
@ -954,7 +954,7 @@ function reactToMessage(
};
}
function resetComposer(): ResetComposerActionType {
export function resetComposer(): ResetComposerActionType {
return {
type: RESET_COMPOSER,
};

View file

@ -132,6 +132,23 @@ import { DAY } from '../../util/durations';
import { isNotNil } from '../../util/isNotNil';
import { PanelType } from '../../types/Panels';
import { startConversation } from '../../util/startConversation';
import { UUIDKind } from '../../types/UUID';
import {
removeLinkPreview,
suspendLinkPreviews,
} from '../../services/LinkPreview';
import type {
ReplaceAttachmentsActionType,
SetFocusActionType,
SetQuotedMessageActionType,
ResetComposerActionType,
} from './composer';
import {
replaceAttachments,
setComposerFocus,
setQuoteByMessageId,
resetComposer,
} from './composer';
// State
@ -884,6 +901,8 @@ export type ConversationActionType =
// Action Creators
export const actions = {
onConversationOpened,
onConversationClosed,
acceptConversation,
acknowledgeGroupMemberNameCollisions,
addMembersToGroup,
@ -909,7 +928,6 @@ export const actions = {
conversationChanged,
conversationRemoved,
conversationStoppedByMissingVerification,
conversationUnloaded,
createGroup,
deleteAvatarFromDisk,
deleteConversation,
@ -1003,23 +1021,37 @@ export const useConversationsActions = (): BoundActionCreatorsMapObject<
typeof actions
> => useBoundActions(actions);
function onArchive(conversationId: string): ShowToastActionType {
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('onArchive: Conversation not found!');
}
function onArchive(
conversationId: string
): ThunkAction<
void,
RootStateType,
unknown,
ConversationUnloadedActionType | ShowToastActionType
> {
return (dispatch, getState) => {
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('onArchive: Conversation not found!');
}
conversation.setArchived(true);
conversation.trigger('unload', 'archive');
conversation.setArchived(true);
return {
type: SHOW_TOAST,
payload: {
toastType: ToastType.ConversationArchived,
parameters: {
conversationId,
onConversationClosed(conversationId, 'archive')(
dispatch,
getState,
undefined
);
dispatch({
type: SHOW_TOAST,
payload: {
toastType: ToastType.ConversationArchived,
parameters: {
conversationId,
},
},
},
});
};
}
function onUndoArchive(
@ -1533,8 +1565,13 @@ function deleteMessage({
function destroyMessages(
conversationId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return async dispatch => {
): ThunkAction<
void,
RootStateType,
unknown,
ConversationUnloadedActionType | NoopActionType
> {
return async (dispatch, getState) => {
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('destroyMessages: No conversation found');
@ -1544,9 +1581,14 @@ function destroyMessages(
name: 'destroymessages',
idForLogging: conversation.idForLogging(),
task: async () => {
conversation.trigger('unload', 'delete messages');
onConversationClosed(conversationId, 'delete messages')(
dispatch,
getState,
undefined
);
await conversation.destroyMessages();
void conversation.updateLastMessage();
drop(conversation.updateLastMessage());
},
});
@ -2240,14 +2282,6 @@ function conversationRemoved(id: string): ConversationRemovedActionType {
},
};
}
function conversationUnloaded(id: string): ConversationUnloadedActionType {
return {
type: CONVERSATION_UNLOADED,
payload: {
id,
},
};
}
function createGroup(
createGroupV2 = groups.createGroupV2
@ -3131,7 +3165,7 @@ export function scrollToMessage(
return;
}
void conversation.loadAndScroll(messageId);
drop(conversation.loadAndScroll(messageId));
};
}
@ -3457,6 +3491,152 @@ function showConversation({
},
};
}
function onConversationOpened(
conversationId: string,
messageId?: string
): ThunkAction<
void,
RootStateType,
unknown,
| ReplaceAttachmentsActionType
| ResetComposerActionType
| SetFocusActionType
| SetQuotedMessageActionType
> {
return async (dispatch, getState) => {
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('onConversationOpened: Conversation not found');
}
conversation.onOpenStart();
if (messageId) {
const message = await getMessageById(messageId);
if (message) {
drop(conversation.loadAndScroll(messageId));
return;
}
log.warn(`onOpened: Did not find message ${messageId}`);
}
const { retryPlaceholders } = window.Signal.Services;
if (retryPlaceholders) {
await retryPlaceholders.findByConversationAndMarkOpened(conversation.id);
}
const loadAndUpdate = async () => {
drop(
Promise.all([
conversation.loadNewestMessages(undefined, undefined),
conversation.updateLastMessage(),
conversation.updateUnread(),
])
);
};
drop(loadAndUpdate());
dispatch(setComposerFocus(conversation.id));
const quotedMessageId = conversation.get('quotedMessageId');
if (quotedMessageId) {
setQuoteByMessageId(conversation.id, quotedMessageId)(
dispatch,
getState,
undefined
);
}
drop(conversation.fetchLatestGroupV2Data());
strictAssert(
conversation.throttledMaybeMigrateV1Group !== undefined,
'Conversation model should be initialized'
);
drop(conversation.throttledMaybeMigrateV1Group());
strictAssert(
conversation.throttledFetchSMSOnlyUUID !== undefined,
'Conversation model should be initialized'
);
drop(conversation.throttledFetchSMSOnlyUUID());
const ourUuid = window.textsecure.storage.user.getUuid(UUIDKind.ACI);
if (
!isGroup(conversation.attributes) ||
(ourUuid && conversation.hasMember(ourUuid))
) {
strictAssert(
conversation.throttledGetProfiles !== undefined,
'Conversation model should be initialized'
);
await conversation.throttledGetProfiles();
}
drop(conversation.updateVerified());
replaceAttachments(
conversation.get('id'),
conversation.get('draftAttachments') || []
)(dispatch, getState, undefined);
dispatch(resetComposer());
};
}
function onConversationClosed(
conversationId: string,
reason: string
): ThunkAction<void, RootStateType, unknown, ConversationUnloadedActionType> {
return async dispatch => {
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('onConversationClosed: Conversation not found');
}
log.info(
'unloading conversation',
conversation.idForLogging(),
'due to:',
reason
);
if (conversation.get('draftChanged')) {
if (conversation.hasDraft()) {
const now = Date.now();
const activeAt = conversation.get('active_at') || now;
conversation.set({
active_at: activeAt,
draftChanged: false,
draftTimestamp: now,
timestamp: now,
});
} else {
conversation.set({
draftChanged: false,
draftTimestamp: null,
});
}
window.Signal.Data.updateConversation(conversation.attributes);
drop(conversation.updateLastMessage());
}
removeLinkPreview();
suspendLinkPreviews();
dispatch({
type: CONVERSATION_UNLOADED,
payload: {
id: conversationId,
},
});
};
}
function showArchivedConversations(): ShowArchivedConversationsActionType {
return {
type: 'SHOW_ARCHIVED_CONVERSATIONS',

View file

@ -1,30 +0,0 @@
// Copyright 2021-2022 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 { ErrorBoundary } from '../../components/ErrorBoundary';
import type { PropsType } from '../smart/ConversationView';
import { SmartConversationView } from '../smart/ConversationView';
export const createConversationView = (
store: Store,
props: PropsType
): React.ReactElement => (
<Provider store={store}>
<ErrorBoundary
name="createConversationView"
closeView={() => {
window.reduxActions.conversations.showConversation({
conversationId: undefined,
messageId: undefined,
});
}}
>
<SmartConversationView {...props} />
</ErrorBoundary>
</Provider>
);

View file

@ -8,9 +8,7 @@ import type { MenuItemConstructorOptions } from 'electron';
import type { MenuActionType } from '../../types/menu';
import { App } from '../../components/App';
import { SmartCallManager } from './CallManager';
import { SmartCustomizingPreferredReactionsModal } from './CustomizingPreferredReactionsModal';
import { SmartGlobalModalContainer } from './GlobalModalContainer';
import { SmartLeftPane } from './LeftPane';
import { SmartLightbox } from './Lightbox';
import { SmartStories } from './Stories';
import { SmartStoryViewer } from './StoryViewer';
@ -28,10 +26,14 @@ import {
shouldShowStoriesView,
} from '../selectors/stories';
import { getHideMenuBar } from '../selectors/items';
import { getIsCustomizingPreferredReactions } from '../selectors/preferredReactions';
import { mapDispatchToProps } from '../actions';
import { ErrorBoundary } from '../../components/ErrorBoundary';
import { ModalContainer } from '../../components/ModalContainer';
import { SmartInbox } from './Inbox';
function renderInbox(): JSX.Element {
return <SmartInbox />;
}
const mapStateToProps = (state: StateType) => {
const i18n = getIntl(state);
@ -40,7 +42,6 @@ const mapStateToProps = (state: StateType) => {
...state.app,
i18n,
localeMessages: getLocaleMessages(state),
isCustomizingPreferredReactions: getIsCustomizingPreferredReactions(state),
isMaximized: getIsMainWindowMaximized(state),
isFullScreen: getIsMainWindowFullScreen(state),
menuOptions: getMenuOptions(state),
@ -51,11 +52,7 @@ const mapStateToProps = (state: StateType) => {
<SmartCallManager />
</ModalContainer>
),
renderCustomizingPreferredReactionsModal: () => (
<SmartCustomizingPreferredReactionsModal />
),
renderGlobalModalContainer: () => <SmartGlobalModalContainer />,
renderLeftPane: () => <SmartLeftPane />,
renderLightbox: () => <SmartLightbox />,
isShowingStoriesView: shouldShowStoriesView(state),
renderStories: (closeView: () => unknown) => (
@ -69,6 +66,7 @@ const mapStateToProps = (state: StateType) => {
<SmartStoryViewer />
</ErrorBoundary>
),
renderInbox,
requestVerification: (
type: 'sms' | 'voice',
number: string,
@ -85,9 +83,6 @@ const mapStateToProps = (state: StateType) => {
registerSingleDevice: (number: string, code: string): Promise<void> => {
return window.getAccountManager().registerSingleDevice(number, code);
},
selectedConversationId: state.conversations.selectedConversationId,
selectedMessage: state.conversations.selectedMessage,
selectedMessageSource: state.conversations.selectedMessageSource,
theme: getTheme(state),
executeMenuRole: (role: MenuItemConstructorOptions['role']): void => {

View file

@ -23,17 +23,20 @@ import { SmartPendingInvites } from './PendingInvites';
import { SmartStickerManager } from './StickerManager';
import { SmartTimeline } from './Timeline';
import { getIntl } from '../selectors/user';
import { getTopPanel } from '../selectors/conversations';
import {
getSelectedConversationId,
getTopPanel,
} from '../selectors/conversations';
import { useComposerActions } from '../ducks/composer';
import { useConversationsActions } from '../ducks/conversations';
export type PropsType = {
conversationId: string;
};
export function SmartConversationView(): JSX.Element {
const conversationId = useSelector(getSelectedConversationId);
if (!conversationId) {
throw new Error('SmartConversationView: No selected conversation');
}
export function SmartConversationView({
conversationId,
}: PropsType): JSX.Element {
const topPanel = useSelector<StateType, PanelRenderType | undefined>(
getTopPanel
);

70
ts/state/smart/Inbox.tsx Normal file
View file

@ -0,0 +1,70 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { useSelector } from 'react-redux';
import type { AppStateType } from '../ducks/app';
import type { ConversationsStateType } from '../ducks/conversations';
import type { StateType } from '../reducer';
import { Inbox } from '../../components/Inbox';
import { getIntl } from '../selectors/user';
import { SmartConversationView } from './ConversationView';
import { SmartCustomizingPreferredReactionsModal } from './CustomizingPreferredReactionsModal';
import { SmartLeftPane } from './LeftPane';
import { useConversationsActions } from '../ducks/conversations';
import { useGlobalModalActions } from '../ducks/globalModals';
import { getIsCustomizingPreferredReactions } from '../selectors/preferredReactions';
function renderConversationView() {
return <SmartConversationView />;
}
function renderCustomizingPreferredReactionsModal() {
return <SmartCustomizingPreferredReactionsModal />;
}
function renderLeftPane() {
return <SmartLeftPane />;
}
export function SmartInbox(): JSX.Element {
const i18n = useSelector(getIntl);
const isCustomizingPreferredReactions = useSelector(
getIsCustomizingPreferredReactions
);
const { hasInitialLoadCompleted } = useSelector<StateType, AppStateType>(
state => state.app
);
const { selectedConversationId, selectedMessage, selectedMessageSource } =
useSelector<StateType, ConversationsStateType>(
state => state.conversations
);
const {
onConversationClosed,
onConversationOpened,
scrollToMessage,
showConversation,
} = useConversationsActions();
const { showWhatsNewModal } = useGlobalModalActions();
return (
<Inbox
hasInitialLoadCompleted={hasInitialLoadCompleted}
i18n={i18n}
isCustomizingPreferredReactions={isCustomizingPreferredReactions}
onConversationClosed={onConversationClosed}
onConversationOpened={onConversationOpened}
renderConversationView={renderConversationView}
renderCustomizingPreferredReactionsModal={
renderCustomizingPreferredReactionsModal
}
renderLeftPane={renderLeftPane}
scrollToMessage={scrollToMessage}
selectedConversationId={selectedConversationId}
selectedMessage={selectedMessage}
selectedMessageSource={selectedMessageSource}
showConversation={showConversation}
showWhatsNewModal={showWhatsNewModal}
/>
);
}

View file

@ -131,15 +131,14 @@ void (async () => {
}
const timeline = window.locator(
'.timeline-wrapper, .ConversationView__template .react-wrapper'
'.timeline-wrapper, .conversation .ConversationView'
);
const deltaList = new Array<number>();
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
debug('finding composition input and clicking it');
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('_react=CompositionInput');

View file

@ -83,15 +83,14 @@ void (async () => {
}
const timeline = window.locator(
'.timeline-wrapper, .ConversationView__template .react-wrapper'
'.timeline-wrapper, .conversation .ConversationView'
);
const deltaList = new Array<number>();
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
debug('finding composition input and clicking it');
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('_react=CompositionInput');

View file

@ -94,8 +94,7 @@ describe('pnp/learn', function needsName() {
debug('Send message to contactA');
{
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');

View file

@ -100,8 +100,7 @@ describe('pnp/PNI Change', function needsName() {
debug('Send message to contactA');
{
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
@ -202,8 +201,7 @@ describe('pnp/PNI Change', function needsName() {
debug('Send message to contactA');
{
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
@ -306,8 +304,7 @@ describe('pnp/PNI Change', function needsName() {
debug('Send message to contactA');
{
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
@ -367,8 +364,7 @@ describe('pnp/PNI Change', function needsName() {
debug('Send message to contactB');
{
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
@ -444,8 +440,7 @@ describe('pnp/PNI Change', function needsName() {
debug('Send message to contactA');
{
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
@ -536,8 +531,7 @@ describe('pnp/PNI Change', function needsName() {
debug('Send message to contactA');
{
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');

View file

@ -110,8 +110,7 @@ describe('pnp/PNI Signature', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
const conversationStack = window.locator('.conversation-stack');
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
debug('creating a stranger');
@ -269,8 +268,7 @@ describe('pnp/PNI Signature', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
debug('opening conversation with the pni contact');

View file

@ -122,8 +122,7 @@ describe('storage service', function needsName() {
debug('Enter message text');
const composeArea = window.locator(
'.composition-area-wrapper, ' +
'.ConversationView__template .react-wrapper'
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('_react=CompositionInput');

View file

@ -115,7 +115,7 @@ describe('storage service', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
const conversationView = window.locator(
'.ConversationView__template > .react-wrapper'
'.conversation > .ConversationView'
);
debug('sending two sticker pack links');

File diff suppressed because it is too large Load diff

View file

@ -19,101 +19,6 @@
"expression": "\\bdocument.write(ln)?\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-$(",
"expression": "\\$\\(",
"reason": "Potential XSS",
"excludedModules": ["node_modules/prelude-ls"]
},
{
"name": "jQuery-html(",
"expression": "\\bhtml\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-append(",
"expression": "\\bappend\\(",
"reason": "Potential XSS",
"excludedModules": [
"components/bytebuffer",
"components/protobuf",
"node_modules/google-libphonenumber",
"node_modules/handlebars"
]
},
{
"name": "jQuery-appendTo(",
"expression": "\\bappendTo\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-insertAfter(",
"expression": "\\binsertAfter\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-insertBefore(",
"expression": "\\binsertBefore\\(",
"reason": "Potential XSS",
"excludedModules": ["node_modules/react-dom"]
},
{
"name": "jQuery-prepend(",
"expression": "\\bprepend\\(",
"reason": "Potential XSS",
"excludedModules": ["components/bytebuffer", "node_modules/handlebars"]
},
{
"name": "jQuery-prependTo(",
"expression": "\\bprependTo\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-wrap(",
"expression": "\\bwrap\\(",
"reason": "Potential XSS",
"excludedModules": [
"components/bytebuffer",
"components/protobuf",
"node_modules/handlebars",
"node_modules/lodash"
]
},
{
"name": "jQuery-wrapInner(",
"expression": "\\bwrapInner\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-wrapAll(",
"expression": "\\bwrapAll\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-before(",
"expression": "\\bbefore\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-after(",
"expression": "\\bafter\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-globalEval(",
"expression": "\\bglobalEval\\(",
"reason": "Arbitrary code execution"
},
{
"name": "jQuery-getScript(",
"expression": "\\bgetScript\\(",
"reason": "Arbitrary code execution"
},
{
"name": "jQuery-load(",
"expression": "\\bload\\(",
"reason": "Arbitrary code execution"
},
{
"name": "React-ref",
"expression": "\\bref(\\s)*=\\b",

View file

@ -1,244 +0,0 @@
// Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable camelcase, max-classes-per-file */
import type { ReactElement } from 'react';
import * as Backbone from 'backbone';
import * as ReactDOM from 'react-dom';
import { render } from 'mustache';
import type { ConversationModel } from '../models/conversations';
import { getMessageById } from '../messages/getMessageById';
import { strictAssert } from '../util/assert';
import { isGroup } from '../util/whatTypeOfConversation';
import * as log from '../logging/log';
import { createConversationView } from '../state/roots/createConversationView';
import {
removeLinkPreview,
suspendLinkPreviews,
} from '../services/LinkPreview';
import { UUIDKind } from '../types/UUID';
export class ConversationView extends window.Backbone.View<ConversationModel> {
// Sub-views
private conversationView?: Backbone.View;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(...args: Array<any>) {
super(...args);
// Events on Conversation model
this.listenTo(this.model, 'destroy', this.stopListening);
// These are triggered by InboxView
this.listenTo(this.model, 'opened', this.onOpened);
this.listenTo(this.model, 'unload', (reason: string) =>
this.unload(`model trigger - ${reason}`)
);
this.render();
this.setupConversationView();
window.reduxActions.composer.replaceAttachments(
this.model.get('id'),
this.model.get('draftAttachments') || []
);
}
// We need this ignore because the backbone types really want this to be a string
// property, but the property isn't set until after super() is run, meaning that this
// classname wouldn't be applied when Backbone creates our el.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
className(): string {
return 'conversation';
}
// Same situation as className().
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
id(): string {
return `conversation-${this.model.cid}`;
}
// Backbone.View<ConversationModel> is demanded as the return type here, and we can't
// satisfy it because of the above difference in signature: className is a function
// when it should be a plain string property.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
render(): ConversationView {
const template = $('#conversation').html();
this.$el.html(render(template, {}));
return this;
}
setupConversationView(): void {
// setupCompositionArea
window.reduxActions.composer.resetComposer();
// createConversationView root
const JSX = createConversationView(window.reduxStore, {
conversationId: this.model.id,
});
this.conversationView = new ReactWrapperView({ JSX });
this.$('.ConversationView__template').append(this.conversationView.el);
}
unload(reason: string): void {
log.info(
'unloading conversation',
this.model.idForLogging(),
'due to:',
reason
);
const { conversationUnloaded } = window.reduxActions.conversations;
if (conversationUnloaded) {
conversationUnloaded(this.model.id);
}
if (this.model.get('draftChanged')) {
if (this.model.hasDraft()) {
const now = Date.now();
const active_at = this.model.get('active_at') || now;
this.model.set({
active_at,
draftChanged: false,
draftTimestamp: now,
timestamp: now,
});
} else {
this.model.set({
draftChanged: false,
draftTimestamp: null,
});
}
window.Signal.Data.updateConversation(this.model.attributes);
void this.model.updateLastMessage();
}
this.conversationView?.remove();
removeLinkPreview();
suspendLinkPreviews();
this.remove();
}
async onOpened(messageId: string): Promise<void> {
this.model.onOpenStart();
if (messageId) {
const message = await getMessageById(messageId);
if (message) {
void this.model.loadAndScroll(messageId);
return;
}
log.warn(`onOpened: Did not find message ${messageId}`);
}
const { retryPlaceholders } = window.Signal.Services;
if (retryPlaceholders) {
await retryPlaceholders.findByConversationAndMarkOpened(this.model.id);
}
const loadAndUpdate = async () => {
void Promise.all([
this.model.loadNewestMessages(undefined, undefined),
this.model.updateLastMessage(),
this.model.updateUnread(),
]);
};
void loadAndUpdate();
window.reduxActions.composer.setComposerFocus(this.model.id);
const quotedMessageId = this.model.get('quotedMessageId');
if (quotedMessageId) {
window.reduxActions.composer.setQuoteByMessageId(
this.model.id,
quotedMessageId
);
}
void this.model.fetchLatestGroupV2Data();
strictAssert(
this.model.throttledMaybeMigrateV1Group !== undefined,
'Conversation model should be initialized'
);
void this.model.throttledMaybeMigrateV1Group();
strictAssert(
this.model.throttledFetchSMSOnlyUUID !== undefined,
'Conversation model should be initialized'
);
void this.model.throttledFetchSMSOnlyUUID();
const ourUuid = window.textsecure.storage.user.getUuid(UUIDKind.ACI);
if (
!isGroup(this.model.attributes) ||
(ourUuid && this.model.hasMember(ourUuid))
) {
strictAssert(
this.model.throttledGetProfiles !== undefined,
'Conversation model should be initialized'
);
await this.model.throttledGetProfiles();
}
void this.model.updateVerified();
}
}
class ReactWrapperView extends Backbone.View {
private readonly onClose?: () => unknown;
private JSX: ReactElement;
constructor({
className,
onClose,
JSX,
}: Readonly<{
className?: string;
onClose?: () => unknown;
JSX: ReactElement;
}>) {
super();
this.className = className ?? 'react-wrapper';
this.JSX = JSX;
this.onClose = onClose;
this.render();
}
update(JSX: ReactElement): void {
this.JSX = JSX;
this.render();
}
override render(): this {
this.el.className = this.className;
ReactDOM.render(this.JSX, this.el);
return this;
}
override remove(): this {
if (this.onClose) {
this.onClose();
}
ReactDOM.unmountComponentAtNode(this.el);
Backbone.View.prototype.remove.call(this);
return this;
}
}
window.Whisper.ConversationView = ConversationView;

10
ts/window.d.ts vendored
View file

@ -77,7 +77,6 @@ import type { Address } from './types/Address';
import type { QualifiedAddress } from './types/QualifiedAddress';
import type { CI } from './CI';
import type { IPCEventsType } from './util/createIPCEvents';
import type { ConversationView } from './views/conversation_view';
import type { SignalContextType } from './windows/context';
import type * as Message2 from './types/Message2';
import type { initializeMigrations } from './signal';
@ -358,13 +357,4 @@ export type WhisperType = {
deliveryReceiptQueue: PQueue;
deliveryReceiptBatcher: BatcherType<Receipt>;
events: Backbone.Events;
// Backbone views
ConversationView: typeof ConversationView;
// Note: we can no longer use 'View.extend' once we've moved to Typescript's preferred
// 'extend View' syntax. Thus, we'll need to typescriptify most of it at once.
InboxView: typeof Backbone.View;
};

View file

@ -6,6 +6,5 @@
import '../../models/messages';
import '../../models/conversations';
import '../../views/conversation_view';
import '../../SignalProtocolStore';
import '../../background';

View file

@ -3441,7 +3441,7 @@
jest-matcher-utils "^27.0.0"
pretty-format "^27.0.0"
"@types/jquery@*", "@types/jquery@3.5.6":
"@types/jquery@*":
version "3.5.6"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.6.tgz#97ac8e36dccd8ad8ed3f3f3b48933614d9fd8cf0"
integrity sha512-SmgCQRzGPId4MZQKDj9Hqc6kSXFNWZFHpELkyK8AQhf8Zr6HKfCzFv9ZC1Fv3FyQttJZOlap3qYb12h61iZAIg==
@ -11248,11 +11248,6 @@ jpeg-js@^0.4.1, jpeg-js@^0.4.2:
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==
jquery@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9"
integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==
js-sdsl@^4.1.4:
version "4.1.5"
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a"
@ -12705,11 +12700,6 @@ multicast-dns@^6.0.1:
dns-packet "^1.3.1"
thunky "^1.0.2"
mustache@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0"
integrity sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"