Merge pull request #1823 from Bilb/add-message-bottom-keep-scroll

fix emoji inserted in the middle of mentions + drag on images
pull/1825/head
Audric Ackermann 4 years ago committed by GitHub
commit 5d537cc06d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -98,9 +98,9 @@
"pify": "3.0.0", "pify": "3.0.0",
"protobufjs": "^6.9.0", "protobufjs": "^6.9.0",
"rc-slider": "^8.7.1", "rc-slider": "^8.7.1",
"react": "^16.13.1", "react": "^17.0.2",
"react-contexify": "5.0.0", "react-contexify": "5.0.0",
"react-dom": "16.8.3", "react-dom": "^17.0.2",
"react-emoji": "^0.5.0", "react-emoji": "^0.5.0",
"react-emoji-render": "^1.2.4", "react-emoji-render": "^1.2.4",
"react-h5-audio-player": "^3.2.0", "react-h5-audio-player": "^3.2.0",
@ -112,7 +112,6 @@
"react-toastify": "^6.0.9", "react-toastify": "^6.0.9",
"react-use": "^17.2.1", "react-use": "^17.2.1",
"react-virtualized": "9.22.3", "react-virtualized": "9.22.3",
"react-window-infinite-loader": "^1.0.7",
"read-last-lines": "1.3.0", "read-last-lines": "1.3.0",
"redux": "4.0.1", "redux": "4.0.1",
"redux-logger": "3.0.6", "redux-logger": "3.0.6",
@ -158,8 +157,8 @@
"@types/pify": "3.0.2", "@types/pify": "3.0.2",
"@types/qs": "6.5.1", "@types/qs": "6.5.1",
"@types/rc-slider": "^8.6.5", "@types/rc-slider": "^8.6.5",
"@types/react": "16.8.5", "@types/react": "^17.0.15",
"@types/react-dom": "16.8.2", "@types/react-dom": "^17.0.2",
"@types/react-mentions": "^4.1.1", "@types/react-mentions": "^4.1.1",
"@types/react-mic": "^12.4.1", "@types/react-mic": "^12.4.1",
"@types/react-portal": "^4.0.2", "@types/react-portal": "^4.0.2",

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useCallback, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder'; import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder';
@ -71,6 +71,11 @@ const AvatarImage = (props: {
}) => { }) => {
const { avatarPath, base64Data, name, imageBroken, handleImageError } = props; const { avatarPath, base64Data, name, imageBroken, handleImageError } = props;
const onDragStart = useCallback((e: any) => {
e.preventDefault();
return false;
}, []);
if ((!avatarPath && !base64Data) || imageBroken) { if ((!avatarPath && !base64Data) || imageBroken) {
return null; return null;
} }
@ -79,6 +84,7 @@ const AvatarImage = (props: {
return ( return (
<img <img
onError={handleImageError} onError={handleImageError}
onDragStart={onDragStart}
alt={window.i18n('contactAvatarAlt', [name])} alt={window.i18n('contactAvatarAlt', [name])}
src={dataToDisplay} src={dataToDisplay}
/> />

@ -1,6 +1,6 @@
// tslint:disable:react-a11y-anchors // tslint:disable:react-a11y-anchors
import React, { useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import is from '@sindresorhus/is'; import is from '@sindresorhus/is';
@ -187,7 +187,7 @@ const Icon = ({
}) => ( }) => (
<div <div
style={{ style={{
...styles.object, ...(styles.object as any),
...colorSVG(url, Colors.ICON_SECONDARY), ...colorSVG(url, Colors.ICON_SECONDARY),
maxWidth: 200, maxWidth: 200,
}} }}
@ -211,6 +211,10 @@ export const LightboxObject = ({
const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType);
const onDragStart = useCallback((e:any) => {
e.preventDefault();
return false; }, []);
const playVideo = () => { const playVideo = () => {
if (!videoRef) { if (!videoRef) {
return; return;
@ -245,7 +249,14 @@ export const LightboxObject = ({
}); });
if (isImageTypeSupported) { if (isImageTypeSupported) {
return <img style={styles.object} alt={window.i18n('lightboxImageAlt')} src={urlToLoad} />; return (
<img
style={styles.object as any}
onDragStart={onDragStart}
alt={window.i18n('lightboxImageAlt')}
src={urlToLoad}
/>
);
} }
const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType); const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType);
@ -259,7 +270,7 @@ export const LightboxObject = ({
ref={videoRef} ref={videoRef}
onClick={playVideo} onClick={playVideo}
controls={true} controls={true}
style={styles.object} style={styles.object as any}
key={urlToLoad} key={urlToLoad}
> >
<source src={urlToLoad} /> <source src={urlToLoad} />
@ -302,8 +313,8 @@ export const Lightbox = (props: Props) => {
}; };
return ( return (
<div style={styles.container} role="dialog"> <div style={styles.container as any} role="dialog">
<div style={styles.mainContainer}> <div style={styles.mainContainer as any}>
<div style={styles.controlsOffsetPlaceholder} /> <div style={styles.controlsOffsetPlaceholder} />
<div <div
style={styles.objectParentContainer} style={styles.objectParentContainer}
@ -311,7 +322,7 @@ export const Lightbox = (props: Props) => {
ref={containerRef} ref={containerRef}
role="button" role="button"
> >
<div style={styles.objectContainer}> <div style={styles.objectContainer as any}>
{!is.undefined(contentType) ? ( {!is.undefined(contentType) ? (
<LightboxObject <LightboxObject
objectURL={objectURL} objectURL={objectURL}
@ -320,10 +331,10 @@ export const Lightbox = (props: Props) => {
onObjectClick={onObjectClick} onObjectClick={onObjectClick}
/> />
) : null} ) : null}
{caption ? <div style={styles.caption}>{caption}</div> : null} {caption ? <div style={styles.caption as any}>{caption}</div> : null}
</div> </div>
</div> </div>
<div style={styles.controls}> <div style={styles.controls as any}>
<Flex flex="1 1 auto"> <Flex flex="1 1 auto">
<IconButton <IconButton
type="close" type="close"
@ -339,7 +350,7 @@ export const Lightbox = (props: Props) => {
) : null} ) : null}
</div> </div>
</div> </div>
<div style={styles.navigationContainer}> <div style={styles.navigationContainer as any}>
{onPrevious ? ( {onPrevious ? (
<IconButton type="previous" onClick={onPrevious} theme={theme} /> <IconButton type="previous" onClick={onPrevious} theme={theme} />
) : ( ) : (

@ -8,9 +8,9 @@ import { SizeClassType } from '../util/emoji';
import { RenderTextCallbackType } from '../types/Util'; import { RenderTextCallbackType } from '../types/Util';
interface Props { type Props = {
text: string; text: string;
} };
const renderNewLines: RenderTextCallbackType = ({ text, key }) => ( const renderNewLines: RenderTextCallbackType = ({ text, key }) => (
<AddNewLines key={key} text={text} /> <AddNewLines key={key} text={text} />
@ -28,56 +28,27 @@ const renderEmoji = ({
renderNonEmoji: RenderTextCallbackType; renderNonEmoji: RenderTextCallbackType;
}) => <Emojify key={key} text={text} sizeClass={sizeClass} renderNonEmoji={renderNonEmoji} />; }) => <Emojify key={key} text={text} sizeClass={sizeClass} renderNonEmoji={renderNonEmoji} />;
export class MessageBodyHighlight extends React.Component<Props> { export const MessageBodyHighlight = (props: Props) => {
public render() { const { text } = props;
const { text } = this.props; const results: Array<any> = [];
const results: Array<any> = []; const FIND_BEGIN_END = /<<left>>(.+?)<<right>>/g;
const FIND_BEGIN_END = /<<left>>(.+?)<<right>>/g;
let match = FIND_BEGIN_END.exec(text); let match = FIND_BEGIN_END.exec(text);
let last = 0; let last = 0;
let count = 1; let count = 1;
if (!match) { if (!match) {
return <MessageBody disableJumbomoji={true} disableLinks={true} text={text} />; return <MessageBody disableJumbomoji={true} disableLinks={true} text={text} />;
} }
const sizeClass = '';
while (match) {
if (last < match.index) {
const beforeText = text.slice(last, match.index);
results.push(
renderEmoji({
text: beforeText,
sizeClass,
key: count++,
renderNonEmoji: renderNewLines,
})
);
}
const [, toHighlight] = match;
results.push(
<span className="module-message-body__highlight" key={count++}>
{renderEmoji({
text: toHighlight,
sizeClass,
key: count++,
renderNonEmoji: renderNewLines,
})}
</span>
);
// @ts-ignore const sizeClass = '';
last = FIND_BEGIN_END.lastIndex;
match = FIND_BEGIN_END.exec(text);
}
if (last < text.length) { while (match) {
if (last < match.index) {
const beforeText = text.slice(last, match.index);
results.push( results.push(
renderEmoji({ renderEmoji({
text: text.slice(last), text: beforeText,
sizeClass, sizeClass,
key: count++, key: count++,
renderNonEmoji: renderNewLines, renderNonEmoji: renderNewLines,
@ -85,6 +56,33 @@ export class MessageBodyHighlight extends React.Component<Props> {
); );
} }
return results; const [, toHighlight] = match;
results.push(
<span className="module-message-body__highlight" key={count++}>
{renderEmoji({
text: toHighlight,
sizeClass,
key: count++,
renderNonEmoji: renderNewLines,
})}
</span>
);
// @ts-ignore
last = FIND_BEGIN_END.lastIndex;
match = FIND_BEGIN_END.exec(text);
} }
}
if (last < text.length) {
results.push(
renderEmoji({
text: text.slice(last),
sizeClass,
key: count++,
renderNonEmoji: renderNewLines,
})
);
}
return results;
};

@ -34,7 +34,7 @@ export const ContactName = (props: Props) => {
: {}) as React.CSSProperties; : {}) as React.CSSProperties;
const textProfile = profileName || name || window.i18n('anonymous'); const textProfile = profileName || name || window.i18n('anonymous');
const profileElement = shouldShowProfile ? ( const profileElement = shouldShowProfile ? (
<span style={styles} className={`${prefix}__profile-name`}> <span style={styles as any} className={`${prefix}__profile-name`}>
<Emojify text={textProfile} /> <Emojify text={textProfile} />
</span> </span>
) : null; ) : null;

@ -219,6 +219,8 @@ export type ConversationHeaderTitleProps = {
const ConversationHeaderTitle = () => { const ConversationHeaderTitle = () => {
const headerTitleProps = useSelector(getConversationHeaderTitleProps); const headerTitleProps = useSelector(getConversationHeaderTitleProps);
const notificationSetting = useSelector(getCurrentNotificationSettingText);
const marginXS = useTheme().common.margins.xs;
if (!headerTitleProps) { if (!headerTitleProps) {
return null; return null;
} }
@ -256,12 +258,10 @@ const ConversationHeaderTitle = () => {
memberCountText = i18n('members', [count]); memberCountText = i18n('members', [count]);
} }
const notificationSetting = useSelector(getCurrentNotificationSettingText);
const notificationSubtitle = notificationSetting const notificationSubtitle = notificationSetting
? window.i18n('notificationSubtitle', notificationSetting) ? window.i18n('notificationSubtitle', notificationSetting)
: null; : null;
const title = profileName || name || phoneNumber; const title = profileName || name || phoneNumber;
const marginXS = useTheme().common.margins.xs;
return ( return (
<div className="module-conversation-header__title"> <div className="module-conversation-header__title">

@ -43,6 +43,7 @@ export class Emojify extends React.Component<Props> {
while (match) { while (match) {
if (last < match.index) { if (last < match.index) {
const textWithNoEmoji = text.slice(last, match.index); const textWithNoEmoji = text.slice(last, match.index);
results.push( results.push(
renderNonEmoji({ renderNonEmoji({
text: textWithNoEmoji, text: textWithNoEmoji,

@ -1,4 +1,4 @@
import React from 'react'; import React, { useCallback } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Spinner } from '../basic/Spinner'; import { Spinner } from '../basic/Spinner';
@ -57,6 +57,11 @@ export const Image = (props: Props) => {
width, width,
} = props; } = props;
const onDragStart = useCallback((e: any) => {
e.preventDefault();
return false;
}, []);
const { caption, pending } = attachment || { caption: null, pending: true }; const { caption, pending } = attachment || { caption: null, pending: true };
const canClick = onClick && !pending; const canClick = onClick && !pending;
const role = canClick ? 'button' : undefined; const role = canClick ? 'button' : undefined;
@ -106,6 +111,7 @@ export const Image = (props: Props) => {
height={height} height={height}
width={width} width={width}
src={srcData} src={srcData}
onDragStart={onDragStart}
/> />
)} )}
{caption ? ( {caption ? (
@ -113,6 +119,7 @@ export const Image = (props: Props) => {
className="module-image__caption-icon" className="module-image__caption-icon"
src="images/caption-shadow.svg" src="images/caption-shadow.svg"
alt={window.i18n('imageCaptionIconAlt')} alt={window.i18n('imageCaptionIconAlt')}
onDragStart={onDragStart}
/> />
) : null} ) : null}
<div <div

@ -153,11 +153,11 @@ class MessageInner extends React.PureComponent<Props, State> {
clearInterval(this.expirationCheckInterval); clearInterval(this.expirationCheckInterval);
} }
if (this.expiredTimeout) { if (this.expiredTimeout) {
clearTimeout(this.expiredTimeout); global.clearTimeout(this.expiredTimeout);
} }
} }
public componentDidUpdate(prevProps: Props) { public componentDidUpdate() {
this.checkExpired(); this.checkExpired();
} }
@ -726,14 +726,15 @@ class MessageInner extends React.PureComponent<Props, State> {
private onQuoteClick(e: any) { private onQuoteClick(e: any) {
const { quote, multiSelectMode, id } = this.props; const { quote, multiSelectMode, id } = this.props;
e.preventDefault();
e.stopPropagation();
if (!quote) { if (!quote) {
window.log.warn('onQuoteClick: quote not valid'); window.log.warn('onQuoteClick: quote not valid');
return; return;
} }
const quoteId = _.toNumber(quote.messageId); const quoteId = _.toNumber(quote.messageId);
const { authorPhoneNumber, referencedMessageNotFound } = quote; const { authorPhoneNumber, referencedMessageNotFound } = quote;
e.preventDefault();
e.stopPropagation();
if (multiSelectMode && id) { if (multiSelectMode && id) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id)); window.inboxStore?.dispatch(toggleSelectedMessageId(id));
@ -810,7 +811,7 @@ class MessageInner extends React.PureComponent<Props, State> {
const { timestamp, serverTimestamp, authorPhoneNumber, attachments, convoId } = this.props; const { timestamp, serverTimestamp, authorPhoneNumber, attachments, convoId } = this.props;
e.stopPropagation(); e.stopPropagation();
e.preventDefault();
if (!attachments?.length) { if (!attachments?.length) {
return; return;
} }
@ -828,6 +829,7 @@ class MessageInner extends React.PureComponent<Props, State> {
private onClickOnMessageOuterContainer(event: any) { private onClickOnMessageOuterContainer(event: any) {
const { multiSelectMode, id } = this.props; const { multiSelectMode, id } = this.props;
const selection = window.getSelection(); const selection = window.getSelection();
// Text is being selected // Text is being selected
if (selection && selection.type === 'Range') { if (selection && selection.type === 'Range') {
@ -839,7 +841,8 @@ class MessageInner extends React.PureComponent<Props, State> {
if ((!multiSelectMode && target.className === 'text-selectable') || window.contextMenuShown) { if ((!multiSelectMode && target.className === 'text-selectable') || window.contextMenuShown) {
return; return;
} }
event.preventDefault();
event.stopPropagation();
if (id) { if (id) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id)); window.inboxStore?.dispatch(toggleSelectedMessageId(id));
} }

@ -1,6 +1,6 @@
// tslint:disable:react-this-binding-issue // tslint:disable:react-this-binding-issue
import React, { useState } from 'react'; import React, { useCallback, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import * as MIME from '../../../ts/types/MIME'; import * as MIME from '../../../ts/types/MIME';
@ -113,6 +113,11 @@ export const QuoteImage = (props: any) => {
const { loading, urlToLoad } = useEncryptedFileFetch(url, contentType); const { loading, urlToLoad } = useEncryptedFileFetch(url, contentType);
const srcData = !loading ? urlToLoad : ''; const srcData = !loading ? urlToLoad : '';
const onDragStart = useCallback((e:any) => {
e.preventDefault();
return false;
}, []);
const iconElement = icon ? ( const iconElement = icon ? (
<div className="module-quote__icon-container__inner"> <div className="module-quote__icon-container__inner">
<div className="module-quote__icon-container__circle-background"> <div className="module-quote__icon-container__circle-background">
@ -128,7 +133,12 @@ export const QuoteImage = (props: any) => {
return ( return (
<div className="module-quote__icon-container"> <div className="module-quote__icon-container">
<img src={srcData} alt={window.i18n('quoteThumbnailAlt')} onError={handleImageErrorBound} /> <img
src={srcData}
alt={window.i18n('quoteThumbnailAlt')}
onDragStart={onDragStart}
onError={handleImageErrorBound}
/>
{iconElement} {iconElement}
</div> </div>
); );

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useCallback, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome'; import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome';
@ -21,6 +21,11 @@ const MediaGridItemContent = (props: Props) => {
const [imageBroken, setImageBroken] = useState(false); const [imageBroken, setImageBroken] = useState(false);
const { loading, urlToLoad } = useEncryptedFileFetch(urlToDecrypt, contentType); const { loading, urlToLoad } = useEncryptedFileFetch(urlToDecrypt, contentType);
const onDragStart = useCallback((e: any) => {
e.preventDefault();
return false;
}, []);
// data will be url if loading is finished and '' if not // data will be url if loading is finished and '' if not
const srcData = !loading ? urlToLoad : ''; const srcData = !loading ? urlToLoad : '';
@ -52,6 +57,7 @@ const MediaGridItemContent = (props: Props) => {
className="module-media-grid-item__image" className="module-media-grid-item__image"
src={srcData} src={srcData}
onError={onImageError} onError={onImageError}
onDragStart={onDragStart}
/> />
); );
} else if (contentType && isVideoTypeSupported(contentType)) { } else if (contentType && isVideoTypeSupported(contentType)) {
@ -73,6 +79,7 @@ const MediaGridItemContent = (props: Props) => {
className="module-media-grid-item__image" className="module-media-grid-item__image"
src={srcData} src={srcData}
onError={onImageError} onError={onImageError}
onDragStart={onDragStart}
/> />
<div className="module-media-grid-item__circle-overlay"> <div className="module-media-grid-item__circle-overlay">
<div className="module-media-grid-item__play-overlay" /> <div className="module-media-grid-item__play-overlay" />

@ -249,9 +249,9 @@ export const ActionsPanel = () => {
// wait for cleanUpMediasInterval and then start cleaning up medias // wait for cleanUpMediasInterval and then start cleaning up medias
// this would be way easier to just be able to not trigger a call with the setInterval // this would be way easier to just be able to not trigger a call with the setInterval
useEffect(() => { useEffect(() => {
const timeout = global.setTimeout(() => setStartCleanUpMedia(true), cleanUpMediasInterval); const timeout = setTimeout(() => setStartCleanUpMedia(true), cleanUpMediasInterval);
return () => global.clearTimeout(timeout); return () => clearTimeout(timeout);
}, []); }, []);
useInterval( useInterval(

@ -964,6 +964,63 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
this.setState({ message }); this.setState({ message });
} }
private getSelectionBasedOnMentions(index: number) {
// we have to get the real selectionStart/end of an index in the mentions box.
// this is kind of a pain as the mentions box has two inputs, one with the real text, and one with the extracted mentions
// the index shown to the user is actually just the visible part of the mentions (so the part between ᅲ...ᅭ
const matches = this.state.message.match(this.mentionsRegex);
let lastMatchStartIndex = 0;
let lastMatchEndIndex = 0;
let lastRealMatchEndIndex = 0;
if (!matches) {
return index;
}
const mapStartToLengthOfMatches = matches.map(match => {
const displayNameStart = match.indexOf('\uFFD7') + 1;
const displayNameEnd = match.lastIndexOf('\uFFD2');
const displayName = match.substring(displayNameStart, displayNameEnd);
const currentMatchStartIndex = this.state.message.indexOf(match) + lastMatchStartIndex;
lastMatchStartIndex = currentMatchStartIndex;
lastMatchEndIndex = currentMatchStartIndex + match.length;
const realLength = displayName.length + 1;
lastRealMatchEndIndex = lastRealMatchEndIndex + realLength;
// the +1 is for the @
return {
length: displayName.length + 1,
lastRealMatchEndIndex,
start: lastMatchStartIndex,
end: lastMatchEndIndex,
};
});
const beforeFirstMatch = index < mapStartToLengthOfMatches[0].start;
if (beforeFirstMatch) {
// those first char are always just char, so the mentions logic does not come into account
return index;
}
const lastMatchMap = _.last(mapStartToLengthOfMatches);
if (!lastMatchMap) {
return Number.MAX_SAFE_INTEGER;
}
const indexIsAfterEndOfLastMatch = lastMatchMap.lastRealMatchEndIndex <= index;
if (indexIsAfterEndOfLastMatch) {
const lastEnd = lastMatchMap.end;
const diffBetweenEndAndLastRealEnd = index - lastMatchMap.lastRealMatchEndIndex;
return lastEnd + diffBetweenEndAndLastRealEnd - 1;
}
// now this is the hard part, the cursor is currently between the end of the first match and the start of the last match
// for now, just append it to the end
return Number.MAX_SAFE_INTEGER;
}
private onEmojiClick({ colons }: { colons: string }) { private onEmojiClick({ colons }: { colons: string }) {
const messageBox = this.textarea.current; const messageBox = this.textarea.current;
if (!messageBox) { if (!messageBox) {
@ -973,10 +1030,12 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
const { message } = this.state; const { message } = this.state;
const currentSelectionStart = Number(messageBox.selectionStart); const currentSelectionStart = Number(messageBox.selectionStart);
const currentSelectionEnd = Number(messageBox.selectionEnd);
const before = message.slice(0, currentSelectionStart); const realSelectionStart = this.getSelectionBasedOnMentions(currentSelectionStart);
const end = message.slice(currentSelectionEnd);
const before = message.slice(0, realSelectionStart);
const end = message.slice(realSelectionStart);
const newMessage = `${before}${colons}${end}`; const newMessage = `${before}${colons}${end}`;
this.setState({ message: newMessage }, () => { this.setState({ message: newMessage }, () => {

@ -3,42 +3,27 @@ import classNames from 'classnames';
import { Picker } from 'emoji-mart'; import { Picker } from 'emoji-mart';
import { Constants } from '../../../session'; import { Constants } from '../../../session';
interface Props { type Props = {
onEmojiClicked: (emoji: any) => void; onEmojiClicked: (emoji: any) => void;
show: boolean; show: boolean;
} };
interface State { export const SessionEmojiPanel = (props: Props) => {
// FIXME Use Emoji-Mart categories const { onEmojiClicked, show } = props;
category: null;
}
export class SessionEmojiPanel extends React.Component<Props, State> { return (
constructor(props: Props) { <div className={classNames('session-emoji-panel', show && 'show')}>
super(props); <Picker
backgroundImageFn={() => './images/emoji/emoji-sheet-twitter-32.png'}
this.state = { set={'twitter'}
category: null, sheetSize={32}
}; darkMode={true}
} color={Constants.UI.COLORS.GREEN}
showPreview={true}
public render() { title={''}
const { onEmojiClicked, show } = this.props; onSelect={onEmojiClicked}
autoFocus={true}
return ( />
<div className={classNames('session-emoji-panel', show && 'show')}> </div>
<Picker );
backgroundImageFn={() => './images/emoji/emoji-sheet-twitter-32.png'} };
set={'twitter'}
sheetSize={32}
darkMode={true}
color={Constants.UI.COLORS.GREEN}
showPreview={true}
title={''}
onSelect={onEmojiClicked}
autoFocus={true}
/>
</div>
);
}
}

@ -62,7 +62,7 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
public componentWillUnmount() { public componentWillUnmount() {
if (this.timeoutResetQuotedScroll) { if (this.timeoutResetQuotedScroll) {
clearTimeout(this.timeoutResetQuotedScroll); global.clearTimeout(this.timeoutResetQuotedScroll);
} }
} }
@ -170,7 +170,7 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
*/ */
private setupTimeoutResetQuotedHighlightedMessage(messageId: string | undefined) { private setupTimeoutResetQuotedHighlightedMessage(messageId: string | undefined) {
if (this.timeoutResetQuotedScroll) { if (this.timeoutResetQuotedScroll) {
clearTimeout(this.timeoutResetQuotedScroll); global.clearTimeout(this.timeoutResetQuotedScroll);
} }
if (messageId !== undefined) { if (messageId !== undefined) {

@ -118,13 +118,13 @@ const animation = (props: {
}; };
//tslint:disable no-unnecessary-callback-wrapper //tslint:disable no-unnecessary-callback-wrapper
const Svg = styled.svg<StyledSvgProps>` const Svg = React.memo(styled.svg<StyledSvgProps>`
width: ${props => props.width}; width: ${props => props.width};
transform: ${props => `rotate(${props.iconRotation}deg)`}; transform: ${props => `rotate(${props.iconRotation}deg)`};
animation: ${props => animation(props)}; animation: ${props => animation(props)};
border-radius: ${props => props.borderRadius}; border-radius: ${props => props.borderRadius};
filter: ${props => (props.noScale ? `drop-shadow(0px 0px 4px ${props.iconColor})` : '')}; filter: ${props => (props.noScale ? `drop-shadow(0px 0px 4px ${props.iconColor})` : '')};
`; `);
//tslint:enable no-unnecessary-callback-wrapper //tslint:enable no-unnecessary-callback-wrapper
const SessionSvg = (props: { const SessionSvg = (props: {

@ -310,7 +310,7 @@ function _removeJob(id: number) {
} }
if (_jobs[id].timer) { if (_jobs[id].timer) {
clearTimeout(_jobs[id].timer); global.clearTimeout(_jobs[id].timer);
_jobs[id].timer = null; _jobs[id].timer = null;
} }

@ -361,6 +361,7 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
getConversationController() getConversationController()
.get(UserUtils.getOurPubKeyStrFromCache()) .get(UserUtils.getOurPubKeyStrFromCache())
?.get('profileKey') || null; ?.get('profileKey') || null;
profileKey = ourConvoProfileKey ? fromHexToArray(ourConvoProfileKey) : null; profileKey = ourConvoProfileKey ? fromHexToArray(ourConvoProfileKey) : null;
if (!profileKey) { if (!profileKey) {
window.log.info('our profileKey not found. Not reuploading our avatar'); window.log.info('our profileKey not found. Not reuploading our avatar');

@ -20,7 +20,7 @@ import {
saveMessages, saveMessages,
updateConversation, updateConversation,
} from '../../ts/data/data'; } from '../../ts/data/data';
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer, toHex } from '../session/utils/String'; import { toHex } from '../session/utils/String';
import { import {
actions as conversationActions, actions as conversationActions,
conversationChanged, conversationChanged,
@ -300,7 +300,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public setTypingRefreshTimer() { public setTypingRefreshTimer() {
if (this.typingRefreshTimer) { if (this.typingRefreshTimer) {
clearTimeout(this.typingRefreshTimer); global.clearTimeout(this.typingRefreshTimer);
} }
this.typingRefreshTimer = global.setTimeout(this.onTypingRefreshTimeout.bind(this), 10 * 1000); this.typingRefreshTimer = global.setTimeout(this.onTypingRefreshTimeout.bind(this), 10 * 1000);
} }
@ -315,7 +315,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public setTypingPauseTimer() { public setTypingPauseTimer() {
if (this.typingPauseTimer) { if (this.typingPauseTimer) {
clearTimeout(this.typingPauseTimer); global.clearTimeout(this.typingPauseTimer);
} }
this.typingPauseTimer = global.setTimeout(this.onTypingPauseTimeout.bind(this), 10 * 1000); this.typingPauseTimer = global.setTimeout(this.onTypingPauseTimeout.bind(this), 10 * 1000);
} }
@ -329,11 +329,11 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public clearTypingTimers() { public clearTypingTimers() {
if (this.typingPauseTimer) { if (this.typingPauseTimer) {
clearTimeout(this.typingPauseTimer); global.clearTimeout(this.typingPauseTimer);
this.typingPauseTimer = null; this.typingPauseTimer = null;
} }
if (this.typingRefreshTimer) { if (this.typingRefreshTimer) {
clearTimeout(this.typingRefreshTimer); global.clearTimeout(this.typingRefreshTimer);
this.typingRefreshTimer = null; this.typingRefreshTimer = null;
} }
} }
@ -1469,7 +1469,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const wasTyping = !!this.typingTimer; const wasTyping = !!this.typingTimer;
if (this.typingTimer) { if (this.typingTimer) {
clearTimeout(this.typingTimer); global.clearTimeout(this.typingTimer);
this.typingTimer = null; this.typingTimer = null;
} }
@ -1498,7 +1498,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public async clearContactTypingTimer(sender: string) { public async clearContactTypingTimer(sender: string) {
if (!!this.typingTimer) { if (!!this.typingTimer) {
clearTimeout(this.typingTimer); global.clearTimeout(this.typingTimer);
this.typingTimer = null; this.typingTimer = null;
// User was previously typing, but timed out or we received message. State change! // User was previously typing, but timed out or we received message. State change!

@ -20,7 +20,7 @@ export const TTL_DEFAULT = {
export const SWARM_POLLING_TIMEOUT = { export const SWARM_POLLING_TIMEOUT = {
ACTIVE: DURATION.SECONDS * 5, ACTIVE: DURATION.SECONDS * 5,
MEDIUM_ACTIVE: DURATION.SECONDS * 60, MEDIUM_ACTIVE: DURATION.SECONDS * 60,
INACTIVE: DURATION.MINUTES * 60, INACTIVE: DURATION.SECONDS * 120,
}; };
export const PROTOCOLS = { export const PROTOCOLS = {

@ -98,7 +98,7 @@ export async function initiateGroupUpdate(
name: groupName, name: groupName,
members, members,
// remove from the zombies list the zombies not which are not in the group anymore // remove from the zombies list the zombies not which are not in the group anymore
zombies: convo.get('zombies').filter(z => members.includes(z)), zombies: convo.get('zombies')?.filter(z => members.includes(z)),
activeAt: Date.now(), activeAt: Date.now(),
expireTimer: convo.get('expireTimer'), expireTimer: convo.get('expireTimer'),
avatar, avatar,
@ -119,23 +119,28 @@ export async function initiateGroupUpdate(
if (diff.newName?.length) { if (diff.newName?.length) {
const nameOnlyDiff: GroupDiff = { newName: diff.newName }; const nameOnlyDiff: GroupDiff = { newName: diff.newName };
const dbMessageName = await addUpdateMessage(convo, nameOnlyDiff, 'outgoing', Date.now()); const dbMessageName = await addUpdateMessage(convo, nameOnlyDiff, 'outgoing', Date.now());
getMessageController().register(dbMessageName.id, dbMessageName); getMessageController().register(dbMessageName.id as string, dbMessageName);
await sendNewName(convo, diff.newName, dbMessageName.id); await sendNewName(convo, diff.newName, dbMessageName.id as string);
} }
if (diff.joiningMembers?.length) { if (diff.joiningMembers?.length) {
const joiningOnlyDiff: GroupDiff = { joiningMembers: diff.joiningMembers }; const joiningOnlyDiff: GroupDiff = { joiningMembers: diff.joiningMembers };
const dbMessageAdded = await addUpdateMessage(convo, joiningOnlyDiff, 'outgoing', Date.now()); const dbMessageAdded = await addUpdateMessage(convo, joiningOnlyDiff, 'outgoing', Date.now());
getMessageController().register(dbMessageAdded.id, dbMessageAdded); getMessageController().register(dbMessageAdded.id as string, dbMessageAdded);
await sendAddedMembers(convo, diff.joiningMembers, dbMessageAdded.id, updateObj); await sendAddedMembers(convo, diff.joiningMembers, dbMessageAdded.id as string, updateObj);
} }
if (diff.leavingMembers?.length) { if (diff.leavingMembers?.length) {
const leavingOnlyDiff: GroupDiff = { leavingMembers: diff.leavingMembers }; const leavingOnlyDiff: GroupDiff = { leavingMembers: diff.leavingMembers };
const dbMessageLeaving = await addUpdateMessage(convo, leavingOnlyDiff, 'outgoing', Date.now()); const dbMessageLeaving = await addUpdateMessage(convo, leavingOnlyDiff, 'outgoing', Date.now());
getMessageController().register(dbMessageLeaving.id, dbMessageLeaving); getMessageController().register(dbMessageLeaving.id as string, dbMessageLeaving);
const stillMembers = members; const stillMembers = members;
await sendRemovedMembers(convo, diff.leavingMembers, stillMembers, dbMessageLeaving.id); await sendRemovedMembers(
convo,
diff.leavingMembers,
stillMembers,
dbMessageLeaving.id as string
);
} }
await convo.commit(); await convo.commit();
} }
@ -262,7 +267,7 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
const isBlocked = details.blocked || false; const isBlocked = details.blocked || false;
if (conversation.isClosedGroup() || conversation.isMediumGroup()) { if (conversation.isClosedGroup() || conversation.isMediumGroup()) {
await BlockedNumberController.setGroupBlocked(conversation.id, isBlocked); await BlockedNumberController.setGroupBlocked(conversation.id as string, isBlocked);
} }
if (details.admins?.length) { if (details.admins?.length) {
@ -327,12 +332,12 @@ export async function leaveClosedGroup(groupId: string) {
received_at: now, received_at: now,
expireTimer: 0, expireTimer: 0,
}); });
getMessageController().register(dbMessage.id, dbMessage); getMessageController().register(dbMessage.id as string, dbMessage);
// Send the update to the group // Send the update to the group
const ourLeavingMessage = new ClosedGroupMemberLeftMessage({ const ourLeavingMessage = new ClosedGroupMemberLeftMessage({
timestamp: Date.now(), timestamp: Date.now(),
groupId, groupId,
identifier: dbMessage.id, identifier: dbMessage.id as string,
}); });
window?.log?.info(`We are leaving the group ${groupId}. Sending our leaving message.`); window?.log?.info(`We are leaving the group ${groupId}. Sending our leaving message.`);
@ -357,7 +362,7 @@ async function sendNewName(convo: ConversationModel, name: string, messageId: st
// Send the update to the group // Send the update to the group
const nameChangeMessage = new ClosedGroupNameChangeMessage({ const nameChangeMessage = new ClosedGroupNameChangeMessage({
timestamp: Date.now(), timestamp: Date.now(),
groupId, groupId: groupId as string,
identifier: messageId, identifier: messageId,
name, name,
}); });
@ -427,7 +432,7 @@ export async function sendRemovedMembers(
} }
const ourNumber = UserUtils.getOurPubKeyFromCache(); const ourNumber = UserUtils.getOurPubKeyFromCache();
const admins = convo.get('groupAdmins') || []; const admins = convo.get('groupAdmins') || [];
const groupId = convo.get('id'); const groupId = convo.get('id') as string;
const isCurrentUserAdmin = admins.includes(ourNumber.key); const isCurrentUserAdmin = admins.includes(ourNumber.key);
const isUserLeaving = removedMembers.includes(ourNumber.key); const isUserLeaving = removedMembers.includes(ourNumber.key);

@ -565,7 +565,7 @@ export async function retrieveNextMessages(
} }
if (result.status !== 200) { if (result.status !== 200) {
window.log('retrieve result is not 200'); window?.log?.warn('retrieve result is not 200');
return []; return [];
} }

@ -909,7 +909,7 @@ export async function lokiOnionFetch(
throw new Error(ERROR_CODE_NO_CONNECT); throw new Error(ERROR_CODE_NO_CONNECT);
} }
if (e?.message === CLOCK_OUT_OF_SYNC_MESSAGE_ERROR) { if (e?.message === CLOCK_OUT_OF_SYNC_MESSAGE_ERROR) {
window?.log?.warn('Its an clock out of sync error '); window?.log?.warn('Its a clock out of sync error ');
throw new pRetry.AbortError(CLOCK_OUT_OF_SYNC_MESSAGE_ERROR); throw new pRetry.AbortError(CLOCK_OUT_OF_SYNC_MESSAGE_ERROR);
} }
throw e; throw e;

@ -202,6 +202,9 @@ export class SwarmPolling {
const messages = _.uniqBy(_.flatten(results), (x: any) => x.hash); const messages = _.uniqBy(_.flatten(results), (x: any) => x.hash);
if (isGroup) { if (isGroup) {
window?.log?.info(
`Polled for group(${pubkey}): group.pubkey, got ${messages.length} messages back.`
);
// update the last fetched timestamp // update the last fetched timestamp
this.groupPolling = this.groupPolling.map(group => { this.groupPolling = this.groupPolling.map(group => {
if (PubKey.isEqual(pubkey, group.pubkey)) { if (PubKey.isEqual(pubkey, group.pubkey)) {

@ -47,7 +47,7 @@ export async function start(options: any = {}) {
export function stop() { export function stop() {
enabled = false; enabled = false;
if (timeout) { if (timeout) {
clearTimeout(timeout); global.clearTimeout(timeout);
timeout = null; timeout = null;
} }
} }

@ -23,7 +23,7 @@ export const createTaskWithTimeout = (task: any, id: string, givenTimeout?: numb
const localTimer = timer; const localTimer = timer;
if (localTimer) { if (localTimer) {
timer = null; timer = null;
clearTimeout(localTimer); global.clearTimeout(localTimer);
} }
} catch (error) { } catch (error) {
window?.log?.error( window?.log?.error(

@ -122,7 +122,7 @@ const getValidClosedGroups = async (convos: Array<ConversationModel>) => {
c => c =>
!!c.get('active_at') && !!c.get('active_at') &&
c.isMediumGroup() && c.isMediumGroup() &&
c.get('members').includes(ourPubKey) && c.get('members')?.includes(ourPubKey) &&
!c.get('left') && !c.get('left') &&
!c.get('isKickedFromGroup') && !c.get('isKickedFromGroup') &&
!c.isBlocked() && !c.isBlocked() &&
@ -138,7 +138,7 @@ const getValidClosedGroups = async (convos: Array<ConversationModel>) => {
} }
return new ConfigurationMessageClosedGroup({ return new ConfigurationMessageClosedGroup({
publicKey: groupPubKey, publicKey: groupPubKey as string,
name: c.get('name') || '', name: c.get('name') || '',
members: c.get('members') || [], members: c.get('members') || [],
admins: c.get('groupAdmins') || [], admins: c.get('groupAdmins') || [],
@ -188,7 +188,7 @@ const getValidContacts = (convos: Array<ConversationModel>) => {
} }
return new ConfigurationMessageContact({ return new ConfigurationMessageContact({
publicKey: c.id, publicKey: c.id as string,
displayName: c.getLokiProfile()?.displayName, displayName: c.getLokiProfile()?.displayName,
profilePictureURL: c.get('avatarPointer'), profilePictureURL: c.get('avatarPointer'),
profileKey: !profileKeyForContact?.length ? undefined : profileKeyForContact, profileKey: !profileKeyForContact?.length ? undefined : profileKeyForContact,

@ -16,6 +16,7 @@ import {
import { LightBoxOptions } from '../../components/session/conversation/SessionConversation'; import { LightBoxOptions } from '../../components/session/conversation/SessionConversation';
import { ReplyingToMessageProps } from '../../components/session/conversation/SessionCompositionBox'; import { ReplyingToMessageProps } from '../../components/session/conversation/SessionCompositionBox';
import { QuotedAttachmentType } from '../../components/conversation/Quote'; import { QuotedAttachmentType } from '../../components/conversation/Quote';
import { perfEnd, perfStart } from '../../session/utils/Performance';
export type MessageModelProps = { export type MessageModelProps = {
propsForMessage: PropsForMessage; propsForMessage: PropsForMessage;
@ -599,10 +600,12 @@ const conversationsSlice = createSlice({
}> }>
> >
) { ) {
perfStart('messagesAdded');
action.payload.forEach(added => { action.payload.forEach(added => {
// tslint:disable-next-line: no-parameter-reassignment // tslint:disable-next-line: no-parameter-reassignment
state = handleMessageAdded(state, added); state = handleMessageAdded(state, added);
}); });
perfEnd('messagesAdded', 'messagesAdded');
return state; return state;
}, },

@ -80,7 +80,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP ConversationTypeEnum.GROUP
); );
convo.set('active_at', Date.now() - 3555); convo.set('active_at', Date.now() - 3555);
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq( expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.ACTIVE SWARM_POLLING_TIMEOUT.ACTIVE
); );
}); });
@ -91,7 +91,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP ConversationTypeEnum.GROUP
); );
convo.set('active_at', undefined); convo.set('active_at', undefined);
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq( expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.INACTIVE SWARM_POLLING_TIMEOUT.INACTIVE
); );
}); });
@ -102,7 +102,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP ConversationTypeEnum.GROUP
); );
convo.set('active_at', Date.now() - 1000 * 3600 * 23); convo.set('active_at', Date.now() - 1000 * 3600 * 23);
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq( expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE
); );
}); });
@ -113,7 +113,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP ConversationTypeEnum.GROUP
); );
convo.set('active_at', Date.now() - 1000 * 3600 * 25); convo.set('active_at', Date.now() - 1000 * 3600 * 25);
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq( expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.INACTIVE SWARM_POLLING_TIMEOUT.INACTIVE
); );
}); });
@ -150,7 +150,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP ConversationTypeEnum.GROUP
); );
convo.set('active_at', Date.now()); convo.set('active_at', Date.now());
const groupConvoPubkey = PubKey.cast(convo.id); const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey); swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true); await swarmPolling.start(true);
@ -167,7 +167,7 @@ describe('SwarmPolling', () => {
); );
convo.set('active_at', 1); convo.set('active_at', 1);
const groupConvoPubkey = PubKey.cast(convo.id); const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey); swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true); await swarmPolling.start(true);
@ -184,7 +184,7 @@ describe('SwarmPolling', () => {
); );
convo.set('active_at', 1); convo.set('active_at', 1);
const groupConvoPubkey = PubKey.cast(convo.id); const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey); swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true); await swarmPolling.start(true);
@ -202,7 +202,7 @@ describe('SwarmPolling', () => {
); );
convo.set('active_at', Date.now()); convo.set('active_at', Date.now());
const groupConvoPubkey = PubKey.cast(convo.id); const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey); swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true); await swarmPolling.start(true);
clock.tick(6000); clock.tick(6000);
@ -222,7 +222,7 @@ describe('SwarmPolling', () => {
); );
convo.set('active_at', Date.now()); convo.set('active_at', Date.now());
const groupConvoPubkey = PubKey.cast(convo.id); const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey); swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true); await swarmPolling.start(true);
@ -243,7 +243,7 @@ describe('SwarmPolling', () => {
); );
convo.set('active_at', Date.now()); convo.set('active_at', Date.now());
const groupConvoPubkey = PubKey.cast(convo.id); const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey); swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true); await swarmPolling.start(true);
@ -268,7 +268,7 @@ describe('SwarmPolling', () => {
); );
convo.set('active_at', Date.now()); convo.set('active_at', Date.now());
groupConvoPubkey = PubKey.cast(convo.id); groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey); swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true); await swarmPolling.start(true);
}); });

@ -692,10 +692,10 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-dom@16.8.2": "@types/react-dom@^17.0.2":
version "16.8.2" version "17.0.9"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.2.tgz#9bd7d33f908b243ff0692846ef36c81d4941ad12" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
integrity sha512-MX7n1wq3G/De15RGAAqnmidzhr2Y9O/ClxPxyqaNg96pGyeXUYPSvujgzEVpLo9oIP4Wn1UETl+rxTN02KEpBw== integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
@ -753,14 +753,6 @@
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^2.2.0" csstype "^2.2.0"
"@types/react@16.8.5":
version "16.8.5"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.5.tgz#03b9a6597bc20f6eaaed43f377a160f7e41c2b90"
integrity sha512-8LRySaaSJVLNZb2dbOGvGmzn88cbAfrgDpuWy+6lLgQ0OJFgHHvyuaCX4/7ikqJlpmCPf4uazJAZcfTQRdJqdQ==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/react@^16.8.3": "@types/react@^16.8.3":
version "16.14.7" version "16.14.7"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.7.tgz#b62bd8cc4675d6fe3976126cdd208deda267f1fb" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.7.tgz#b62bd8cc4675d6fe3976126cdd208deda267f1fb"
@ -770,6 +762,15 @@
"@types/scheduler" "*" "@types/scheduler" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/react@^17.0.15":
version "17.0.15"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.15.tgz#c7533dc38025677e312606502df7656a6ea626d0"
integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/redux-logger@3.0.7": "@types/redux-logger@3.0.7":
version "3.0.7" version "3.0.7"
resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.7.tgz#163f6f6865c69c21d56f9356dc8d741718ec0db0" resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.7.tgz#163f6f6865c69c21d56f9356dc8d741718ec0db0"
@ -7496,15 +7497,14 @@ react-contexify@5.0.0:
dependencies: dependencies:
clsx "^1.1.1" clsx "^1.1.1"
react-dom@16.8.3: react-dom@^17.0.2:
version "16.8.3" version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.3.tgz#ae236029e66210783ac81999d3015dfc475b9c32" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA== integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types "^15.6.2" scheduler "^0.20.2"
scheduler "^0.13.3"
react-emoji-render@^1.2.4: react-emoji-render@^1.2.4:
version "1.2.4" version "1.2.4"
@ -7649,19 +7649,13 @@ react-virtualized@9.22.3:
prop-types "^15.7.2" prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4" react-lifecycles-compat "^3.0.4"
react-window-infinite-loader@^1.0.7: react@^17.0.2:
version "1.0.7" version "17.0.2"
resolved "https://registry.yarnpkg.com/react-window-infinite-loader/-/react-window-infinite-loader-1.0.7.tgz#958ef1a689d20dce122ef377583acd987760aee8" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-wg3LWkUpG21lhv+cZvNy+p0+vtclZw+9nP2vO6T9PKT50EN1cUq37Dq6FzcM38h/c2domE0gsUhb6jHXtGogAA== integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
react@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types "^15.6.2"
read-config-file@6.0.0: read-config-file@6.0.0:
version "6.0.0" version "6.0.0"
@ -8213,10 +8207,10 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
scheduler@^0.13.3: scheduler@^0.20.2:
version "0.13.6" version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"

Loading…
Cancel
Save