New top-level React root: <App />

This commit is contained in:
Josh Perez 2021-06-14 15:01:00 -04:00 committed by GitHub
parent 9a1f722545
commit 173771d34b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 457 additions and 266 deletions

View File

@ -258,7 +258,7 @@ and [iOS](https://github.com/signalapp/Signal-iOS/blob/master/BUILDING.md) proje
Then you can set up your development build of Signal Desktop as normal. If you've already
set up as a standalone install, you can switch by opening the DevTools (View -> Toggle
Developer Tools) and entering this into the Console and pressing enter: `window.owsDesktopApp.appView.openInstaller();`
Developer Tools) and entering this into the Console and pressing enter: `window.reduxActions.app.openInstaller();`
## Changing to production

View File

@ -333,7 +333,7 @@
<script type='text/javascript' src='js/reliable_trigger.js'></script>
<script type='text/javascript' src='js/database.js'></script>
<script type='text/javascript' src='js/storage.js'></script>
<script type='text/javascript' src='libtextsecure/protocol_wrapper.js'></script>
<script type='text/javascript' src='libtextsecure/storage/user.js'></script>
<script type='text/javascript' src='libtextsecure/storage/unprocessed.js'></script>
@ -368,7 +368,6 @@
<script type="text/javascript" src="js/views/phone-input-view.js"></script>
<script type='text/javascript' src='js/views/safety_number_change_dialog_view.js'></script>
<script type='text/javascript' src='js/views/standalone_registration_view.js'></script>
<script type='text/javascript' src='js/views/app_view.js'></script>
<script type='text/javascript' src='js/views/clear_data_view.js'></script>
<script type='text/javascript' src='js/wall_clock_listener.js'></script>

View File

@ -80,6 +80,7 @@ const {
const {
createConversationHeader,
} = require('../../ts/state/roots/createConversationHeader');
const { createApp } = require('../../ts/state/roots/createApp');
const { createCallManager } = require('../../ts/state/roots/createCallManager');
const {
createForwardMessageModal,
@ -120,6 +121,7 @@ const {
} = require('../../ts/state/roots/createShortcutGuideModal');
const { createStore } = require('../../ts/state/createStore');
const appDuck = require('../../ts/state/ducks/app');
const callingDuck = require('../../ts/state/ducks/calling');
const conversationsDuck = require('../../ts/state/ducks/conversations');
const emojisDuck = require('../../ts/state/ducks/emojis');
@ -356,6 +358,7 @@ exports.setup = (options = {}) => {
};
const Roots = {
createApp,
createCallManager,
createChatColorPicker,
createCompositionArea,
@ -379,6 +382,7 @@ exports.setup = (options = {}) => {
};
const Ducks = {
app: appDuck,
calling: callingDuck,
conversations: conversationsDuck,
emojis: emojisDuck,

View File

@ -1,154 +0,0 @@
// Copyright 2017-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global Backbone, Whisper, storage, _, ConversationController, $ */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
function resolveTheme() {
const theme = storage.get('theme-setting') || 'system';
if (theme === 'system') {
return window.systemTheme;
}
return theme;
}
Whisper.AppView = Backbone.View.extend({
initialize() {
this.inboxView = null;
this.installView = null;
this.applyTheme();
this.applyHideMenu();
window.subscribeToSystemThemeChange(() => {
this.applyTheme();
});
},
events: {
openInbox: 'openInbox',
},
applyTheme() {
const theme = resolveTheme();
this.$el
.removeClass('light-theme')
.removeClass('dark-theme')
.addClass(`${theme}-theme`);
},
applyHideMenu() {
const hideMenuBar = storage.get('hide-menu-bar', false);
window.setAutoHideMenuBar(hideMenuBar);
window.setMenuBarVisibility(!hideMenuBar);
},
openView(view) {
this.el.innerHTML = '';
this.el.append(view.el);
this.delegateEvents();
},
openDebugLog() {
this.closeDebugLog();
this.debugLogView = new Whisper.DebugLogView();
this.debugLogView.$el.appendTo(this.el);
},
closeDebugLog() {
if (this.debugLogView) {
this.debugLogView.remove();
this.debugLogView = null;
}
},
openInstaller(options = {}) {
window.addSetupMenuItems();
this.resetViews();
const installView = new Whisper.InstallView(options);
this.installView = installView;
this.openView(this.installView);
},
closeInstaller() {
if (this.installView) {
this.installView.remove();
this.installView = null;
}
},
openStandalone() {
if (window.getEnvironment() !== 'production') {
window.addSetupMenuItems();
this.resetViews();
this.standaloneView = new Whisper.StandaloneRegistrationView();
this.openView(this.standaloneView);
}
},
closeStandalone() {
if (this.standaloneView) {
this.standaloneView.remove();
this.standaloneView = null;
}
},
resetViews() {
this.closeInstaller();
this.closeStandalone();
},
openInbox(options = {}) {
// The inbox can be created before the 'empty' event fires or afterwards. If
// before, it's straightforward: the onEmpty() handler below updates the
// view directly, and we're in good shape. If we create the inbox late, we
// need to be sure that the current value of initialLoadComplete is provided
// so its loading screen doesn't stick around forever.
// Two primary techniques at play for this situation:
// - background.js has two openInbox() calls, and passes initalLoadComplete
// directly via the options parameter.
// - in other situations openInbox() will be called with no options. So this
// view keeps track of whether onEmpty() has ever been called with
// this.initialLoadComplete. An example of this: on a phone-pairing setup.
_.defaults(options, { initialLoadComplete: this.initialLoadComplete });
window.log.info('open inbox');
this.closeInstaller();
if (!this.inboxView) {
// We create the inbox immediately so we don't miss an update to
// this.initialLoadComplete between the start of this method and the
// creation of inboxView.
this.inboxView = new Whisper.InboxView({
window,
initialLoadComplete: options.initialLoadComplete,
});
return ConversationController.loadPromise().then(() => {
this.openView(this.inboxView);
});
}
if (!$.contains(this.el, this.inboxView.el)) {
this.openView(this.inboxView);
}
return Promise.resolve();
},
onEmpty() {
const view = this.inboxView;
this.initialLoadComplete = true;
if (view) {
view.onEmpty();
}
},
onProgress(count) {
const view = this.inboxView;
if (view) {
view.onProgress(count);
}
},
openConversation(id, messageId) {
if (id) {
this.openInbox().then(() => {
this.inboxView.openConversation(id, messageId);
});
}
},
});
})();

View File

@ -106,6 +106,30 @@
this.conversation_stack.unload();
});
window.Whisper.events.on('showConversation', async (id, messageId) => {
const conversation = await ConversationController.getOrCreateAndWait(
id,
'private'
);
conversation.setMarkedUnread(false);
const { openConversationExternal } = window.reduxActions.conversations;
if (openConversationExternal) {
openConversationExternal(conversation.id, messageId);
}
this.conversation_stack.open(conversation, messageId);
this.focusConversation();
});
window.Whisper.events.on('loadingProgress', count => {
const view = this.appLoadingScreen;
if (view) {
view.updateProgress(count);
}
});
if (!options.initialLoadComplete) {
this.appLoadingScreen = new Whisper.AppLoadingScreen();
this.appLoadingScreen.render();
@ -209,12 +233,6 @@
}
}
},
onProgress(count) {
const view = this.appLoadingScreen;
if (view) {
view.updateProgress(count);
}
},
focusConversation(e) {
if (e && this.$(e.target).closest('.placeholder').length) {
return;
@ -231,22 +249,6 @@
reloadBackgroundPage() {
window.location.reload();
},
async openConversation(id, messageId) {
const conversation = await ConversationController.getOrCreateAndWait(
id,
'private'
);
conversation.setMarkedUnread(false);
const { openConversationExternal } = window.reduxActions.conversations;
if (openConversationExternal) {
openConversationExternal(conversation.id, messageId);
}
this.conversation_stack.open(conversation, messageId);
this.focusConversation();
},
closeRecording(e) {
if (e && this.$(e.target).closest('.capture-audio').length > 0) {
return;

View File

@ -26,15 +26,15 @@ body {
--title-bar-drag-area-height: 28px;
--draggable-app-region: drag;
}
}
body.light-theme {
background-color: $color-white;
color: $color-gray-90;
}
body.dark-theme {
background-color: $color-gray-95;
color: $color-gray-05;
&.light-theme {
background-color: $color-white;
color: $color-gray-90;
}
&.dark-theme {
background-color: $color-gray-95;
color: $color-gray-05;
}
}
::-webkit-scrollbar {

View File

@ -0,0 +1,12 @@
.App {
height: 100%;
&.light-theme {
background-color: $color-white;
color: $color-gray-90;
}
&.dark-theme {
background-color: $color-gray-95;
color: $color-gray-05;
}
}

View File

@ -28,6 +28,7 @@
// New style: components
@import './components/AddGroupMembersModal.scss';
@import './components/App.scss';
@import './components/Avatar.scss';
@import './components/AvatarInput.scss';
@import './components/Button.scss';

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { isNumber } from 'lodash';
import { render } from 'react-dom';
import {
DecryptionErrorMessage,
PlaintextContent,
@ -300,10 +301,8 @@ export async function startApp(): Promise<void> {
window.log.info('environment:', window.getEnvironment());
let idleDetector: WhatIsThis;
let initialLoadComplete = false;
let newVersion = false;
window.owsDesktopApp = {};
window.document.title = window.getTitle();
window.Whisper.KeyChangeListener.init(window.textsecure.storage.protocol);
@ -905,6 +904,9 @@ export async function startApp(): Promise<void> {
const ourUuid = window.textsecure.storage.user.getUuid();
const ourConversationId = window.ConversationController.getOurConversationId();
const themeSetting = window.Events.getThemeSetting();
const theme = themeSetting === 'system' ? window.systemTheme : themeSetting;
const initialState = {
conversations: {
conversationLookup: window.Signal.Util.makeLookup(conversations, 'id'),
@ -943,7 +945,7 @@ export async function startApp(): Promise<void> {
platform: window.platform,
i18n: window.i18n,
interactionMode: window.getInteractionMode(),
theme: window.Events.getThemeSetting(),
theme,
},
};
@ -955,6 +957,10 @@ export async function startApp(): Promise<void> {
// Binding these actions to our redux store and exposing them allows us to update
// redux when things change in the backbone world.
actions.app = window.Signal.State.bindActionCreators(
window.Signal.State.Ducks.app.actions,
store.dispatch
);
actions.calling = window.Signal.State.bindActionCreators(
window.Signal.State.Ducks.calling.actions,
store.dispatch
@ -1547,17 +1553,11 @@ export async function startApp(): Promise<void> {
}
window.Whisper.events.on('setupAsNewDevice', () => {
const { appView } = window.owsDesktopApp;
if (appView) {
appView.openInstaller();
}
window.reduxActions.app.openInstaller();
});
window.Whisper.events.on('setupAsStandalone', () => {
const { appView } = window.owsDesktopApp;
if (appView) {
appView.openStandalone();
}
window.reduxActions.app.openStandalone();
});
window.Whisper.events.on('powerMonitorSuspend', () => {
@ -1712,10 +1712,13 @@ export async function startApp(): Promise<void> {
});
cancelInitializationMessage();
const appView = new window.Whisper.AppView({
el: $('body'),
});
window.owsDesktopApp.appView = appView;
render(
window.Signal.State.Roots.createApp(window.reduxStore),
document.body
);
const hideMenuBar = window.storage.get('hide-menu-bar', false);
window.setAutoHideMenuBar(hideMenuBar);
window.setMenuBarVisibility(!hideMenuBar);
window.Whisper.WallClockListener.init(window.Whisper.events);
window.Whisper.ExpiringMessagesListener.init(window.Whisper.events);
@ -1723,22 +1726,14 @@ export async function startApp(): Promise<void> {
if (window.Signal.Util.Registration.everDone()) {
connect();
appView.openInbox({
initialLoadComplete,
});
window.reduxActions.app.openInbox();
} else {
appView.openInstaller();
window.reduxActions.app.openInstaller();
}
window.Whisper.events.on('showDebugLog', () => {
appView.openDebugLog();
});
window.Whisper.events.on('unauthorized', () => {
appView.inboxView.networkStatusView.update();
});
window.Whisper.events.on('contactsync', () => {
if (appView.installView) {
appView.openInbox();
if (window.reduxStore.getState().app.isShowingInstaller) {
window.reduxActions.app.openInbox();
}
});
@ -1747,20 +1742,12 @@ export async function startApp(): Promise<void> {
window.Whisper.Notifications.fastClear()
);
window.Whisper.events.on('showConversation', (id, messageId) => {
if (appView) {
appView.openConversation(id, messageId);
}
});
window.Whisper.Notifications.on('click', (id, messageId) => {
window.showWindow();
if (id) {
appView.openConversation(id, messageId);
window.Whisper.events.trigger('showConversation', id, messageId);
} else {
appView.openInbox({
initialLoadComplete,
});
window.reduxActions.app.openInbox();
}
});
@ -2291,11 +2278,6 @@ export async function startApp(): Promise<void> {
}
function onChangeTheme() {
const view = window.owsDesktopApp.appView;
if (view) {
view.applyTheme();
}
if (window.reduxActions && window.reduxActions.user) {
const theme = window.Events.getThemeSetting();
window.reduxActions.user.userChanged({
@ -2352,7 +2334,6 @@ export async function startApp(): Promise<void> {
window.flushAllWaitBatchers(),
]);
window.log.info('onEmpty: All outstanding database requests complete');
initialLoadComplete = true;
window.readyForUpdates();
// Start listeners here, after we get through our queue.
@ -2370,13 +2351,8 @@ export async function startApp(): Promise<void> {
window.Whisper.Notifications.enable();
await onAppView;
const view = window.owsDesktopApp.appView;
if (!view) {
throw new Error('Expected `appView` to be initialized');
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
view.onEmpty();
window.reduxActions.app.initialLoadComplete();
window.logAppLoadedEvent({
processedCount: messageReceiver && messageReceiver.getProcessedCount(),
@ -2456,10 +2432,7 @@ export async function startApp(): Promise<void> {
`incrementProgress: Message count is ${initialStartupCount}`
);
const view = window.owsDesktopApp.appView;
if (view) {
view.onProgress(initialStartupCount);
}
window.Whisper.events.trigger('loadingProgress', initialStartupCount);
}
window.Whisper.events.on('manualConnect', manualConnect);

42
ts/components/App.tsx Normal file
View File

@ -0,0 +1,42 @@
import React from 'react';
import classNames from 'classnames';
import { AppViewType } from '../state/ducks/app';
import { Inbox } from './Inbox';
import { Install } from './Install';
import { StandaloneRegistration } from './StandaloneRegistration';
import { ThemeType } from '../types/Util';
export type PropsType = {
appView: AppViewType;
hasInitialLoadCompleted: boolean;
theme: ThemeType;
};
export const App = ({
appView,
hasInitialLoadCompleted,
theme,
}: PropsType): JSX.Element => {
let contents;
if (appView === AppViewType.Installer) {
contents = <Install />;
} else if (appView === AppViewType.Standalone) {
contents = <StandaloneRegistration />;
} else if (appView === AppViewType.Inbox) {
contents = <Inbox hasInitialLoadCompleted={hasInitialLoadCompleted} />;
}
return (
<div
className={classNames({
App: true,
'light-theme': theme === ThemeType.light,
'dark-theme': theme === ThemeType.dark,
})}
>
{contents}
</div>
);
};

View File

@ -0,0 +1,35 @@
import React, { useEffect, useRef } from 'react';
import * as Backbone from 'backbone';
type PropsType = {
View: typeof Backbone.View;
className?: string;
};
export const 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>
);
};

48
ts/components/Inbox.tsx Normal file
View File

@ -0,0 +1,48 @@
import React, { useEffect, useRef } from 'react';
import * as Backbone from 'backbone';
type InboxViewType = Backbone.View & {
onEmpty?: () => void;
};
type InboxViewOptionsType = Backbone.ViewOptions & {
initialLoadComplete: boolean;
window: typeof window;
};
export type PropsType = {
hasInitialLoadCompleted: boolean;
};
export const Inbox = ({ hasInitialLoadCompleted }: PropsType): JSX.Element => {
const hostRef = useRef<HTMLDivElement | null>(null);
const viewRef = useRef<InboxViewType | undefined>(undefined);
useEffect(() => {
const viewOptions: InboxViewOptionsType = {
el: hostRef.current,
initialLoadComplete: false,
window,
};
const view = new window.Whisper.InboxView(viewOptions);
viewRef.current = view;
return () => {
if (!viewRef || !viewRef.current) {
return;
}
viewRef.current.remove();
viewRef.current = undefined;
};
}, []);
useEffect(() => {
if (hasInitialLoadCompleted && viewRef.current && viewRef.current.onEmpty) {
viewRef.current.onEmpty();
}
}, [hasInitialLoadCompleted, viewRef]);
return <div className="inbox index" ref={hostRef} />;
};

11
ts/components/Install.tsx Normal file
View File

@ -0,0 +1,11 @@
import React from 'react';
import { BackboneHost } from './BackboneHost';
export const Install = (): JSX.Element => {
return (
<BackboneHost
className="full-screen-flow"
View={window.Whisper.InstallView}
/>
);
};

View File

@ -0,0 +1,11 @@
import React from 'react';
import { BackboneHost } from './BackboneHost';
export const StandaloneRegistration = (): JSX.Element => {
return (
<BackboneHost
className="full-screen-flow"
View={window.Whisper.StandaloneRegistrationView}
/>
);
};

View File

@ -1,6 +1,7 @@
// Copyright 2019-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { actions as app } from './ducks/app';
import { actions as audioPlayer } from './ducks/audioPlayer';
import { actions as calling } from './ducks/calling';
import { actions as conversations } from './ducks/conversations';
@ -17,6 +18,7 @@ import { actions as updates } from './ducks/updates';
import { actions as user } from './ducks/user';
export const mapDispatchToProps = {
...app,
...audioPlayer,
...calling,
...conversations,

155
ts/state/ducks/app.ts Normal file
View File

@ -0,0 +1,155 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ThunkAction } from 'redux-thunk';
import { StateType as RootStateType } from '../reducer';
// State
export enum AppViewType {
Blank = 'Blank',
Inbox = 'Inbox',
Installer = 'Installer',
Standalone = 'Standalone',
}
export type AppStateType = {
appView: AppViewType;
hasInitialLoadCompleted: boolean;
};
// Actions
const INITIAL_LOAD_COMPLETE = 'app/INITIAL_LOAD_COMPLETE';
const OPEN_INBOX = 'app/OPEN_INBOX';
const OPEN_INSTALLER = 'app/OPEN_INSTALLER';
const OPEN_STANDALONE = 'app/OPEN_STANDALONE';
type InitialLoadCompleteActionType = {
type: typeof INITIAL_LOAD_COMPLETE;
};
type OpenInboxActionType = {
type: typeof OPEN_INBOX;
};
type OpenInstallerActionType = {
type: typeof OPEN_INSTALLER;
};
type OpenStandaloneActionType = {
type: typeof OPEN_STANDALONE;
};
export type AppActionType =
| InitialLoadCompleteActionType
| OpenInboxActionType
| OpenInstallerActionType
| OpenStandaloneActionType;
export const actions = {
initialLoadComplete,
openInbox,
openInstaller,
openStandalone,
};
function initialLoadComplete(): InitialLoadCompleteActionType {
return {
type: INITIAL_LOAD_COMPLETE,
};
}
function openInbox(): ThunkAction<
void,
RootStateType,
unknown,
OpenInboxActionType
> {
return async dispatch => {
window.log.info('open inbox');
await window.ConversationController.loadPromise();
dispatch({
type: OPEN_INBOX,
});
};
}
function openInstaller(): ThunkAction<
void,
RootStateType,
unknown,
OpenInstallerActionType
> {
return dispatch => {
window.addSetupMenuItems();
dispatch({
type: OPEN_INSTALLER,
});
};
}
function openStandalone(): ThunkAction<
void,
RootStateType,
unknown,
OpenStandaloneActionType
> {
return dispatch => {
if (window.getEnvironment() === 'production') {
return;
}
window.addSetupMenuItems();
dispatch({
type: OPEN_STANDALONE,
});
};
}
// Reducer
export function getEmptyState(): AppStateType {
return {
appView: AppViewType.Blank,
hasInitialLoadCompleted: false,
};
}
export function reducer(
state: Readonly<AppStateType> = getEmptyState(),
action: Readonly<AppActionType>
): AppStateType {
if (action.type === OPEN_INBOX) {
return {
...state,
appView: AppViewType.Inbox,
};
}
if (action.type === INITIAL_LOAD_COMPLETE) {
return {
...state,
hasInitialLoadCompleted: true,
};
}
if (action.type === OPEN_INSTALLER) {
return {
...state,
appView: AppViewType.Installer,
};
}
if (action.type === OPEN_STANDALONE) {
return {
...state,
appView: AppViewType.Standalone,
};
}
return state;
}

View File

@ -3,6 +3,7 @@
import { combineReducers } from 'redux';
import { reducer as app } from './ducks/app';
import { reducer as audioPlayer } from './ducks/audioPlayer';
import { reducer as calling } from './ducks/calling';
import { reducer as conversations } from './ducks/conversations';
@ -19,6 +20,7 @@ import { reducer as updates } from './ducks/updates';
import { reducer as user } from './ducks/user';
export const reducer = combineReducers({
app,
audioPlayer,
calling,
conversations,

View File

@ -0,0 +1,15 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { ReactElement } from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import { SmartApp } from '../smart/App';
export const createApp = (store: Store): ReactElement => (
<Provider store={store}>
<SmartApp />
</Provider>
);

21
ts/state/smart/App.ts Normal file
View File

@ -0,0 +1,21 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { connect } from 'react-redux';
import { App } from '../../components/App';
import { StateType } from '../reducer';
import { getIntl, getTheme } from '../selectors/user';
import { mapDispatchToProps } from '../actions';
const mapStateToProps = (state: StateType) => {
return {
...state.app,
i18n: getIntl(state),
theme: getTheme(state),
};
};
const smart = connect(mapStateToProps, mapDispatchToProps);
export const SmartApp = smart(App);

View File

@ -1,6 +1,7 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { actions as app } from './ducks/app';
import { actions as audioPlayer } from './ducks/audioPlayer';
import { actions as calling } from './ducks/calling';
import { actions as conversations } from './ducks/conversations';
@ -17,6 +18,7 @@ import { actions as updates } from './ducks/updates';
import { actions as user } from './ducks/user';
export type ReduxActions = {
app: typeof app;
audioPlayer: typeof audioPlayer;
calling: typeof calling;
conversations: typeof conversations;

View File

@ -252,30 +252,6 @@
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "DOM-innerHTML",
"path": "js/views/app_view.js",
"line": " this.el.innerHTML = '';",
"reasonCategory": "usageTrusted",
"updated": "2018-09-15T00:38:04.183Z",
"reasonDetail": "Hard-coded string"
},
{
"rule": "jQuery-append(",
"path": "js/views/app_view.js",
"line": " this.el.append(view.el);",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-appendTo(",
"path": "js/views/app_view.js",
"line": " this.debugLogView.$el.appendTo(this.el);",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/banner_view.js",
@ -13510,6 +13486,20 @@
"updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Only used to focus the element."
},
{
"rule": "React-useRef",
"path": "ts/components/BackboneHost.js",
"line": " const hostRef = react_1.useRef(null);",
"reasonCategory": "usageTrusted",
"updated": "2021-06-09T04:02:08.305Z"
},
{
"rule": "React-useRef",
"path": "ts/components/BackboneHost.js",
"line": " const viewRef = react_1.useRef(undefined);",
"reasonCategory": "usageTrusted",
"updated": "2021-06-09T04:02:08.305Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CallNeedPermissionScreen.js",
@ -13812,6 +13802,20 @@
"updated": "2021-03-05T16:51:54.214Z",
"reasonDetail": "Used to handle an <input> element. Only updates the value and selection state."
},
{
"rule": "React-useRef",
"path": "ts/components/Inbox.js",
"line": " const hostRef = react_1.useRef(null);",
"reasonCategory": "usageTrusted",
"updated": "2021-06-08T02:49:25.154Z"
},
{
"rule": "React-useRef",
"path": "ts/components/Inbox.js",
"line": " const viewRef = react_1.useRef(undefined);",
"reasonCategory": "usageTrusted",
"updated": "2021-06-08T02:49:25.154Z"
},
{
"rule": "jQuery-$(",
"path": "ts/components/Intl.js",

10
ts/window.d.ts vendored
View File

@ -42,6 +42,7 @@ import * as Errors from '../js/modules/types/errors';
import { ConversationController } from './ConversationController';
import { ReduxActions } from './state/types';
import { createStore } from './state/createStore';
import { createApp } from './state/roots/createApp';
import { createCallManager } from './state/roots/createCallManager';
import { createChatColorPicker } from './state/roots/createChatColorPicker';
import { createCompositionArea } from './state/roots/createCompositionArea';
@ -62,6 +63,7 @@ import { createShortcutGuideModal } from './state/roots/createShortcutGuideModal
import { createStickerManager } from './state/roots/createStickerManager';
import { createStickerPreviewModal } from './state/roots/createStickerPreviewModal';
import { createTimeline } from './state/roots/createTimeline';
import * as appDuck from './state/ducks/app';
import * as callingDuck from './state/ducks/calling';
import * as conversationsDuck from './state/ducks/conversations';
import * as emojisDuck from './state/ducks/emojis';
@ -163,6 +165,7 @@ declare global {
) => void
) => void;
addSetupMenuItems: () => void;
attachmentDownloadQueue: Array<MessageModel> | undefined;
startupProcessingQueue: StartupQueue | undefined;
baseAttachmentsPath: string;
@ -229,7 +232,6 @@ declare global {
nodeSetImmediate: typeof setImmediate;
normalizeUuids: (obj: any, paths: Array<string>, context: string) => void;
onFullScreenChange: (fullScreen: boolean) => void;
owsDesktopApp: WhatIsThis;
platform: string;
preloadedImages: Array<WhatIsThis>;
reduxActions: ReduxActions;
@ -507,6 +509,7 @@ declare global {
bindActionCreators: typeof bindActionCreators;
createStore: typeof createStore;
Roots: {
createApp: typeof createApp;
createCallManager: typeof createCallManager;
createChatColorPicker: typeof createChatColorPicker;
createCompositionArea: typeof createCompositionArea;
@ -529,6 +532,7 @@ declare global {
createTimeline: typeof createTimeline;
};
Ducks: {
app: typeof appDuck;
calling: typeof callingDuck;
conversations: typeof conversationsDuck;
emojis: typeof emojisDuck;
@ -695,13 +699,15 @@ export type WhisperType = {
ConversationArchivedToast: WhatIsThis;
ConversationUnarchivedToast: WhatIsThis;
ConversationMarkedUnreadToast: WhatIsThis;
AppView: WhatIsThis;
WallClockListener: WhatIsThis;
MessageRequests: WhatIsThis;
BannerView: any;
RecorderView: any;
GroupMemberList: any;
GroupLinkCopiedToast: typeof Backbone.View;
InboxView: typeof window.Whisper.View;
InstallView: typeof window.Whisper.View;
StandaloneRegistrationView: typeof window.Whisper.View;
KeyVerificationPanelView: any;
SafetyNumberChangeDialogView: any;
BodyRangesType: BodyRangesType;