From bea704ecf09f9fac818419e16388e1792584d07c Mon Sep 17 00:00:00 2001 From: audric Date: Wed, 4 Aug 2021 10:52:24 +1000 Subject: [PATCH 1/4] bump react to 17 --- package.json | 9 ++- ts/components/Lightbox.tsx | 20 ++++--- ts/components/conversation/ContactName.tsx | 2 +- ts/components/conversation/Message.tsx | 17 ++++-- ts/components/session/ActionsPanel.tsx | 4 +- .../SessionMessagesListContainer.tsx | 4 +- ts/components/session/icon/SessionIcon.tsx | 4 +- ts/data/data.ts | 2 +- ts/interactions/conversationInteractions.ts | 1 + ts/models/conversation.ts | 14 ++--- ts/session/group/index.ts | 31 +++++----- ts/session/snode_api/SNodeAPI.ts | 2 +- ts/session/snode_api/onions.ts | 2 +- ts/session/snode_api/swarmPolling.ts | 3 + ts/session/utils/AttachmentsDownload.ts | 2 +- ts/session/utils/TaskWithTimeout.ts | 2 +- ts/session/utils/syncUtils.ts | 6 +- ts/state/ducks/conversations.ts | 3 + .../unit/swarm_polling/SwarmPolling_test.ts | 22 +++---- yarn.lock | 58 +++++++++---------- 20 files changed, 110 insertions(+), 98 deletions(-) diff --git a/package.json b/package.json index c4ad55fe5..3fb60e1ba 100644 --- a/package.json +++ b/package.json @@ -98,9 +98,9 @@ "pify": "3.0.0", "protobufjs": "^6.9.0", "rc-slider": "^8.7.1", - "react": "^16.13.1", + "react": "^17.0.2", "react-contexify": "5.0.0", - "react-dom": "16.8.3", + "react-dom": "^17.0.2", "react-emoji": "^0.5.0", "react-emoji-render": "^1.2.4", "react-h5-audio-player": "^3.2.0", @@ -112,7 +112,6 @@ "react-toastify": "^6.0.9", "react-use": "^17.2.1", "react-virtualized": "9.22.3", - "react-window-infinite-loader": "^1.0.7", "read-last-lines": "1.3.0", "redux": "4.0.1", "redux-logger": "3.0.6", @@ -158,8 +157,8 @@ "@types/pify": "3.0.2", "@types/qs": "6.5.1", "@types/rc-slider": "^8.6.5", - "@types/react": "16.8.5", - "@types/react-dom": "16.8.2", + "@types/react": "^17.0.15", + "@types/react-dom": "^17.0.2", "@types/react-mentions": "^4.1.1", "@types/react-mic": "^12.4.1", "@types/react-portal": "^4.0.2", diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx index a60127d3a..c022cd58a 100644 --- a/ts/components/Lightbox.tsx +++ b/ts/components/Lightbox.tsx @@ -187,7 +187,7 @@ const Icon = ({ }) => (
; + return ( + {window.i18n('lightboxImageAlt')} + ); } const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType); @@ -259,7 +261,7 @@ export const LightboxObject = ({ ref={videoRef} onClick={playVideo} controls={true} - style={styles.object} + style={styles.object as any} key={urlToLoad} > @@ -302,8 +304,8 @@ export const Lightbox = (props: Props) => { }; return ( -
-
+
+
{ ref={containerRef} role="button" > -
+
{!is.undefined(contentType) ? ( { onObjectClick={onObjectClick} /> ) : null} - {caption ?
{caption}
: null} + {caption ?
{caption}
: null}
-
+
{ ) : null}
-
+
{onPrevious ? ( ) : ( diff --git a/ts/components/conversation/ContactName.tsx b/ts/components/conversation/ContactName.tsx index b11ab60f7..0bceee81b 100644 --- a/ts/components/conversation/ContactName.tsx +++ b/ts/components/conversation/ContactName.tsx @@ -34,7 +34,7 @@ export const ContactName = (props: Props) => { : {}) as React.CSSProperties; const textProfile = profileName || name || window.i18n('anonymous'); const profileElement = shouldShowProfile ? ( - + ) : null; diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index d7dba0c48..a60dfc8df 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -153,11 +153,11 @@ class MessageInner extends React.PureComponent { clearInterval(this.expirationCheckInterval); } if (this.expiredTimeout) { - clearTimeout(this.expiredTimeout); + global.clearTimeout(this.expiredTimeout); } } - public componentDidUpdate(prevProps: Props) { + public componentDidUpdate() { this.checkExpired(); } @@ -726,14 +726,15 @@ class MessageInner extends React.PureComponent { private onQuoteClick(e: any) { const { quote, multiSelectMode, id } = this.props; + e.preventDefault(); + e.stopPropagation(); if (!quote) { window.log.warn('onQuoteClick: quote not valid'); return; } const quoteId = _.toNumber(quote.messageId); const { authorPhoneNumber, referencedMessageNotFound } = quote; - e.preventDefault(); - e.stopPropagation(); + if (multiSelectMode && id) { window.inboxStore?.dispatch(toggleSelectedMessageId(id)); @@ -810,7 +811,7 @@ class MessageInner extends React.PureComponent { const { timestamp, serverTimestamp, authorPhoneNumber, attachments, convoId } = this.props; e.stopPropagation(); - + e.preventDefault(); if (!attachments?.length) { return; } @@ -828,6 +829,7 @@ class MessageInner extends React.PureComponent { private onClickOnMessageOuterContainer(event: any) { const { multiSelectMode, id } = this.props; + const selection = window.getSelection(); // Text is being selected if (selection && selection.type === 'Range') { @@ -839,7 +841,8 @@ class MessageInner extends React.PureComponent { if ((!multiSelectMode && target.className === 'text-selectable') || window.contextMenuShown) { return; } - + event.preventDefault(); + event.stopPropagation(); if (id) { window.inboxStore?.dispatch(toggleSelectedMessageId(id)); } @@ -857,6 +860,8 @@ class MessageInner extends React.PureComponent { if (target.className === 'text-selectable' || window.contextMenuShown) { return; } + event.preventDefault(); + event.stopPropagation(); } } diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index c94a8d1bc..6ae2da3c8 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -249,9 +249,9 @@ export const ActionsPanel = () => { // 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 useEffect(() => { - const timeout = global.setTimeout(() => setStartCleanUpMedia(true), cleanUpMediasInterval); + const timeout = setTimeout(() => setStartCleanUpMedia(true), cleanUpMediasInterval); - return () => global.clearTimeout(timeout); + return () => clearTimeout(timeout); }, []); useInterval( diff --git a/ts/components/session/conversation/SessionMessagesListContainer.tsx b/ts/components/session/conversation/SessionMessagesListContainer.tsx index 606e24d4c..b47e7115f 100644 --- a/ts/components/session/conversation/SessionMessagesListContainer.tsx +++ b/ts/components/session/conversation/SessionMessagesListContainer.tsx @@ -62,7 +62,7 @@ class SessionMessagesListContainerInner extends React.Component { public componentWillUnmount() { if (this.timeoutResetQuotedScroll) { - clearTimeout(this.timeoutResetQuotedScroll); + global.clearTimeout(this.timeoutResetQuotedScroll); } } @@ -170,7 +170,7 @@ class SessionMessagesListContainerInner extends React.Component { */ private setupTimeoutResetQuotedHighlightedMessage(messageId: string | undefined) { if (this.timeoutResetQuotedScroll) { - clearTimeout(this.timeoutResetQuotedScroll); + global.clearTimeout(this.timeoutResetQuotedScroll); } if (messageId !== undefined) { diff --git a/ts/components/session/icon/SessionIcon.tsx b/ts/components/session/icon/SessionIcon.tsx index e97ab2363..c09b9f5ad 100644 --- a/ts/components/session/icon/SessionIcon.tsx +++ b/ts/components/session/icon/SessionIcon.tsx @@ -118,13 +118,13 @@ const animation = (props: { }; //tslint:disable no-unnecessary-callback-wrapper -const Svg = styled.svg` +const Svg = React.memo(styled.svg` width: ${props => props.width}; transform: ${props => `rotate(${props.iconRotation}deg)`}; animation: ${props => animation(props)}; border-radius: ${props => props.borderRadius}; filter: ${props => (props.noScale ? `drop-shadow(0px 0px 4px ${props.iconColor})` : '')}; -`; +`); //tslint:enable no-unnecessary-callback-wrapper const SessionSvg = (props: { diff --git a/ts/data/data.ts b/ts/data/data.ts index 52c652fcf..11f1eaf53 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -310,7 +310,7 @@ function _removeJob(id: number) { } if (_jobs[id].timer) { - clearTimeout(_jobs[id].timer); + global.clearTimeout(_jobs[id].timer); _jobs[id].timer = null; } diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index d693dda3e..e598d8860 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -361,6 +361,7 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) { getConversationController() .get(UserUtils.getOurPubKeyStrFromCache()) ?.get('profileKey') || null; + profileKey = ourConvoProfileKey ? fromHexToArray(ourConvoProfileKey) : null; if (!profileKey) { window.log.info('our profileKey not found. Not reuploading our avatar'); diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 7cba013e7..0f3a9c2db 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -20,7 +20,7 @@ import { saveMessages, updateConversation, } from '../../ts/data/data'; -import { fromArrayBufferToBase64, fromBase64ToArrayBuffer, toHex } from '../session/utils/String'; +import { toHex } from '../session/utils/String'; import { actions as conversationActions, conversationChanged, @@ -300,7 +300,7 @@ export class ConversationModel extends Backbone.Model { public setTypingRefreshTimer() { if (this.typingRefreshTimer) { - clearTimeout(this.typingRefreshTimer); + global.clearTimeout(this.typingRefreshTimer); } this.typingRefreshTimer = global.setTimeout(this.onTypingRefreshTimeout.bind(this), 10 * 1000); } @@ -315,7 +315,7 @@ export class ConversationModel extends Backbone.Model { public setTypingPauseTimer() { if (this.typingPauseTimer) { - clearTimeout(this.typingPauseTimer); + global.clearTimeout(this.typingPauseTimer); } this.typingPauseTimer = global.setTimeout(this.onTypingPauseTimeout.bind(this), 10 * 1000); } @@ -329,11 +329,11 @@ export class ConversationModel extends Backbone.Model { public clearTypingTimers() { if (this.typingPauseTimer) { - clearTimeout(this.typingPauseTimer); + global.clearTimeout(this.typingPauseTimer); this.typingPauseTimer = null; } if (this.typingRefreshTimer) { - clearTimeout(this.typingRefreshTimer); + global.clearTimeout(this.typingRefreshTimer); this.typingRefreshTimer = null; } } @@ -1468,7 +1468,7 @@ export class ConversationModel extends Backbone.Model { const wasTyping = !!this.typingTimer; if (this.typingTimer) { - clearTimeout(this.typingTimer); + global.clearTimeout(this.typingTimer); this.typingTimer = null; } @@ -1497,7 +1497,7 @@ export class ConversationModel extends Backbone.Model { public async clearContactTypingTimer(sender: string) { if (!!this.typingTimer) { - clearTimeout(this.typingTimer); + global.clearTimeout(this.typingTimer); this.typingTimer = null; // User was previously typing, but timed out or we received message. State change! diff --git a/ts/session/group/index.ts b/ts/session/group/index.ts index 23ba35529..469a2f27d 100644 --- a/ts/session/group/index.ts +++ b/ts/session/group/index.ts @@ -98,7 +98,7 @@ export async function initiateGroupUpdate( name: groupName, members, // 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(), expireTimer: convo.get('expireTimer'), avatar, @@ -119,23 +119,28 @@ export async function initiateGroupUpdate( if (diff.newName?.length) { const nameOnlyDiff: GroupDiff = { newName: diff.newName }; const dbMessageName = await addUpdateMessage(convo, nameOnlyDiff, 'outgoing', Date.now()); - getMessageController().register(dbMessageName.id, dbMessageName); - await sendNewName(convo, diff.newName, dbMessageName.id); + getMessageController().register(dbMessageName.id as string, dbMessageName); + await sendNewName(convo, diff.newName, dbMessageName.id as string); } if (diff.joiningMembers?.length) { const joiningOnlyDiff: GroupDiff = { joiningMembers: diff.joiningMembers }; const dbMessageAdded = await addUpdateMessage(convo, joiningOnlyDiff, 'outgoing', Date.now()); - getMessageController().register(dbMessageAdded.id, dbMessageAdded); - await sendAddedMembers(convo, diff.joiningMembers, dbMessageAdded.id, updateObj); + getMessageController().register(dbMessageAdded.id as string, dbMessageAdded); + await sendAddedMembers(convo, diff.joiningMembers, dbMessageAdded.id as string, updateObj); } if (diff.leavingMembers?.length) { const leavingOnlyDiff: GroupDiff = { leavingMembers: diff.leavingMembers }; const dbMessageLeaving = await addUpdateMessage(convo, leavingOnlyDiff, 'outgoing', Date.now()); - getMessageController().register(dbMessageLeaving.id, dbMessageLeaving); + getMessageController().register(dbMessageLeaving.id as string, dbMessageLeaving); const stillMembers = members; - await sendRemovedMembers(convo, diff.leavingMembers, stillMembers, dbMessageLeaving.id); + await sendRemovedMembers( + convo, + diff.leavingMembers, + stillMembers, + dbMessageLeaving.id as string + ); } await convo.commit(); } @@ -165,7 +170,7 @@ export async function addUpdateMessage( const unread = type === 'incoming'; const message = await convo.addSingleMessage({ - conversationId: convo.get('id'), + conversationId: convo.get('id') as string, type, sent_at: sentAt, received_at: now, @@ -259,7 +264,7 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) { const isBlocked = details.blocked || false; if (conversation.isClosedGroup() || conversation.isMediumGroup()) { - await BlockedNumberController.setGroupBlocked(conversation.id, isBlocked); + await BlockedNumberController.setGroupBlocked(conversation.id as string, isBlocked); } if (details.admins?.length) { @@ -321,12 +326,12 @@ export async function leaveClosedGroup(groupId: string) { received_at: now, expireTimer: 0, }); - getMessageController().register(dbMessage.id, dbMessage); + getMessageController().register(dbMessage.id as string, dbMessage); // Send the update to the group const ourLeavingMessage = new ClosedGroupMemberLeftMessage({ timestamp: Date.now(), groupId, - identifier: dbMessage.id, + identifier: dbMessage.id as string, }); window?.log?.info(`We are leaving the group ${groupId}. Sending our leaving message.`); @@ -351,7 +356,7 @@ async function sendNewName(convo: ConversationModel, name: string, messageId: st // Send the update to the group const nameChangeMessage = new ClosedGroupNameChangeMessage({ timestamp: Date.now(), - groupId, + groupId: groupId as string, identifier: messageId, name, }); @@ -421,7 +426,7 @@ export async function sendRemovedMembers( } const ourNumber = UserUtils.getOurPubKeyFromCache(); const admins = convo.get('groupAdmins') || []; - const groupId = convo.get('id'); + const groupId = convo.get('id') as string; const isCurrentUserAdmin = admins.includes(ourNumber.key); const isUserLeaving = removedMembers.includes(ourNumber.key); diff --git a/ts/session/snode_api/SNodeAPI.ts b/ts/session/snode_api/SNodeAPI.ts index 18b21a452..492d35db3 100644 --- a/ts/session/snode_api/SNodeAPI.ts +++ b/ts/session/snode_api/SNodeAPI.ts @@ -565,7 +565,7 @@ export async function retrieveNextMessages( } if (result.status !== 200) { - window.log('retrieve result is not 200'); + window?.log?.warn('retrieve result is not 200'); return []; } diff --git a/ts/session/snode_api/onions.ts b/ts/session/snode_api/onions.ts index 13778012a..feca249f0 100644 --- a/ts/session/snode_api/onions.ts +++ b/ts/session/snode_api/onions.ts @@ -909,7 +909,7 @@ export async function lokiOnionFetch( throw new Error(ERROR_CODE_NO_CONNECT); } 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 e; diff --git a/ts/session/snode_api/swarmPolling.ts b/ts/session/snode_api/swarmPolling.ts index 512b0c7e2..9a4f64b80 100644 --- a/ts/session/snode_api/swarmPolling.ts +++ b/ts/session/snode_api/swarmPolling.ts @@ -202,6 +202,9 @@ export class SwarmPolling { const messages = _.uniqBy(_.flatten(results), (x: any) => x.hash); if (isGroup) { + window?.log?.info( + `Polled for group(${pubkey}): group.pubkey, got ${messages.length} messages back.` + ); // update the last fetched timestamp this.groupPolling = this.groupPolling.map(group => { if (PubKey.isEqual(pubkey, group.pubkey)) { diff --git a/ts/session/utils/AttachmentsDownload.ts b/ts/session/utils/AttachmentsDownload.ts index 9df66264e..4cdc693ca 100644 --- a/ts/session/utils/AttachmentsDownload.ts +++ b/ts/session/utils/AttachmentsDownload.ts @@ -47,7 +47,7 @@ export async function start(options: any = {}) { export function stop() { enabled = false; if (timeout) { - clearTimeout(timeout); + global.clearTimeout(timeout); timeout = null; } } diff --git a/ts/session/utils/TaskWithTimeout.ts b/ts/session/utils/TaskWithTimeout.ts index 83da297bc..28e1747c7 100644 --- a/ts/session/utils/TaskWithTimeout.ts +++ b/ts/session/utils/TaskWithTimeout.ts @@ -23,7 +23,7 @@ export const createTaskWithTimeout = (task: any, id: string, givenTimeout?: numb const localTimer = timer; if (localTimer) { timer = null; - clearTimeout(localTimer); + global.clearTimeout(localTimer); } } catch (error) { window?.log?.error( diff --git a/ts/session/utils/syncUtils.ts b/ts/session/utils/syncUtils.ts index c2fb62956..e8e373e22 100644 --- a/ts/session/utils/syncUtils.ts +++ b/ts/session/utils/syncUtils.ts @@ -122,7 +122,7 @@ const getValidClosedGroups = async (convos: Array) => { c => !!c.get('active_at') && c.isMediumGroup() && - c.get('members').includes(ourPubKey) && + c.get('members')?.includes(ourPubKey) && !c.get('left') && !c.get('isKickedFromGroup') && !c.isBlocked() && @@ -138,7 +138,7 @@ const getValidClosedGroups = async (convos: Array) => { } return new ConfigurationMessageClosedGroup({ - publicKey: groupPubKey, + publicKey: groupPubKey as string, name: c.get('name') || '', members: c.get('members') || [], admins: c.get('groupAdmins') || [], @@ -188,7 +188,7 @@ const getValidContacts = (convos: Array) => { } return new ConfigurationMessageContact({ - publicKey: c.id, + publicKey: c.id as string, displayName: c.getLokiProfile()?.displayName, profilePictureURL: c.get('avatarPointer'), profileKey: !profileKeyForContact?.length ? undefined : profileKeyForContact, diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 9ec7d65d1..c3f2d6440 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -16,6 +16,7 @@ import { import { LightBoxOptions } from '../../components/session/conversation/SessionConversation'; import { ReplyingToMessageProps } from '../../components/session/conversation/SessionCompositionBox'; import { QuotedAttachmentType } from '../../components/conversation/Quote'; +import { perfEnd, perfStart } from '../../session/utils/Performance'; export type MessageModelProps = { propsForMessage: PropsForMessage; @@ -598,10 +599,12 @@ const conversationsSlice = createSlice({ }> > ) { + perfStart('messagesAdded'); action.payload.forEach(added => { // tslint:disable-next-line: no-parameter-reassignment state = handleMessageAdded(state, added); }); + perfEnd('messagesAdded', 'messagesAdded'); return state; }, diff --git a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts index 5ff3eca58..8a18dd1a9 100644 --- a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts +++ b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts @@ -80,7 +80,7 @@ describe('SwarmPolling', () => { ConversationTypeEnum.GROUP ); 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 ); }); @@ -91,7 +91,7 @@ describe('SwarmPolling', () => { ConversationTypeEnum.GROUP ); 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 ); }); @@ -102,7 +102,7 @@ describe('SwarmPolling', () => { ConversationTypeEnum.GROUP ); 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 ); }); @@ -113,7 +113,7 @@ describe('SwarmPolling', () => { ConversationTypeEnum.GROUP ); 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 ); }); @@ -150,7 +150,7 @@ describe('SwarmPolling', () => { ConversationTypeEnum.GROUP ); convo.set('active_at', Date.now()); - const groupConvoPubkey = PubKey.cast(convo.id); + const groupConvoPubkey = PubKey.cast(convo.id as string); swarmPolling.addGroupId(groupConvoPubkey); await swarmPolling.start(true); @@ -167,7 +167,7 @@ describe('SwarmPolling', () => { ); convo.set('active_at', 1); - const groupConvoPubkey = PubKey.cast(convo.id); + const groupConvoPubkey = PubKey.cast(convo.id as string); swarmPolling.addGroupId(groupConvoPubkey); await swarmPolling.start(true); @@ -184,7 +184,7 @@ describe('SwarmPolling', () => { ); convo.set('active_at', 1); - const groupConvoPubkey = PubKey.cast(convo.id); + const groupConvoPubkey = PubKey.cast(convo.id as string); swarmPolling.addGroupId(groupConvoPubkey); await swarmPolling.start(true); @@ -202,7 +202,7 @@ describe('SwarmPolling', () => { ); convo.set('active_at', Date.now()); - const groupConvoPubkey = PubKey.cast(convo.id); + const groupConvoPubkey = PubKey.cast(convo.id as string); swarmPolling.addGroupId(groupConvoPubkey); await swarmPolling.start(true); clock.tick(6000); @@ -222,7 +222,7 @@ describe('SwarmPolling', () => { ); convo.set('active_at', Date.now()); - const groupConvoPubkey = PubKey.cast(convo.id); + const groupConvoPubkey = PubKey.cast(convo.id as string); swarmPolling.addGroupId(groupConvoPubkey); await swarmPolling.start(true); @@ -243,7 +243,7 @@ describe('SwarmPolling', () => { ); convo.set('active_at', Date.now()); - const groupConvoPubkey = PubKey.cast(convo.id); + const groupConvoPubkey = PubKey.cast(convo.id as string); swarmPolling.addGroupId(groupConvoPubkey); await swarmPolling.start(true); @@ -268,7 +268,7 @@ describe('SwarmPolling', () => { ); convo.set('active_at', Date.now()); - groupConvoPubkey = PubKey.cast(convo.id); + groupConvoPubkey = PubKey.cast(convo.id as string); swarmPolling.addGroupId(groupConvoPubkey); await swarmPolling.start(true); }); diff --git a/yarn.lock b/yarn.lock index fb639bed3..9469bdaa9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -692,10 +692,10 @@ dependencies: "@types/react" "*" -"@types/react-dom@16.8.2": - version "16.8.2" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.2.tgz#9bd7d33f908b243ff0692846ef36c81d4941ad12" - integrity sha512-MX7n1wq3G/De15RGAAqnmidzhr2Y9O/ClxPxyqaNg96pGyeXUYPSvujgzEVpLo9oIP4Wn1UETl+rxTN02KEpBw== +"@types/react-dom@^17.0.2": + version "17.0.9" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" + integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg== dependencies: "@types/react" "*" @@ -753,14 +753,6 @@ "@types/prop-types" "*" 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": version "16.14.7" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.7.tgz#b62bd8cc4675d6fe3976126cdd208deda267f1fb" @@ -770,6 +762,15 @@ "@types/scheduler" "*" 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": version "3.0.7" 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: clsx "^1.1.1" -react-dom@16.8.3: - version "16.8.3" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.3.tgz#ae236029e66210783ac81999d3015dfc475b9c32" - integrity sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA== +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.3" + scheduler "^0.20.2" react-emoji-render@^1.2.4: version "1.2.4" @@ -7649,19 +7649,13 @@ react-virtualized@9.22.3: prop-types "^15.7.2" react-lifecycles-compat "^3.0.4" -react-window-infinite-loader@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/react-window-infinite-loader/-/react-window-infinite-loader-1.0.7.tgz#958ef1a689d20dce122ef377583acd987760aee8" - integrity sha512-wg3LWkUpG21lhv+cZvNy+p0+vtclZw+9nP2vO6T9PKT50EN1cUq37Dq6FzcM38h/c2domE0gsUhb6jHXtGogAA== - -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== +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" read-config-file@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" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.13.3: - version "0.13.6" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" - integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" From 0658cb539a4ef6446d8e99be9cb92cb839722c57 Mon Sep 17 00:00:00 2001 From: audric Date: Wed, 4 Aug 2021 10:52:38 +1000 Subject: [PATCH 2/4] poll more often for inactive closed groups the issue is that when you start the app, a closed group active at is maybe a day old. so you poll for this pubkey. if the call is a success, all good, you get the latest messages and update the active_at. but if the first call fails (even after retrying), this group will be marked as inactive and will be polled only a tiny little bit until we get new messages. So this commit make sure that we poll more often for closed groups which are considered inactive --- ts/session/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/session/constants.ts b/ts/session/constants.ts index f65b4e708..0ed905b8f 100644 --- a/ts/session/constants.ts +++ b/ts/session/constants.ts @@ -20,7 +20,7 @@ export const TTL_DEFAULT = { export const SWARM_POLLING_TIMEOUT = { ACTIVE: DURATION.SECONDS * 5, MEDIUM_ACTIVE: DURATION.SECONDS * 60, - INACTIVE: DURATION.MINUTES * 60, + INACTIVE: DURATION.SECONDS * 120, }; export const PROTOCOLS = { From 5d6c2d94ffeb60d6d5c47d08a93d65315068b154 Mon Sep 17 00:00:00 2001 From: audric Date: Wed, 4 Aug 2021 14:56:00 +1000 Subject: [PATCH 3/4] fix emoji being inserted into mentions identifier if the cursor is before the first mention => insert it correctly if the cursor is after the last mention => insert it correctly if the cursor is between those two => insert it at the end of the composition box --- ts/components/MessageBodyHighlight.tsx | 94 +++++++++---------- ts/components/conversation/Emojify.tsx | 1 + ts/components/conversation/Message.tsx | 2 - .../conversation/SessionCompositionBox.tsx | 65 ++++++++++++- .../conversation/SessionEmojiPanel.tsx | 55 ++++------- 5 files changed, 129 insertions(+), 88 deletions(-) diff --git a/ts/components/MessageBodyHighlight.tsx b/ts/components/MessageBodyHighlight.tsx index 2bf6e39ec..f42cfe1f8 100644 --- a/ts/components/MessageBodyHighlight.tsx +++ b/ts/components/MessageBodyHighlight.tsx @@ -8,9 +8,9 @@ import { SizeClassType } from '../util/emoji'; import { RenderTextCallbackType } from '../types/Util'; -interface Props { +type Props = { text: string; -} +}; const renderNewLines: RenderTextCallbackType = ({ text, key }) => ( @@ -28,56 +28,27 @@ const renderEmoji = ({ renderNonEmoji: RenderTextCallbackType; }) => ; -export class MessageBodyHighlight extends React.Component { - public render() { - const { text } = this.props; - const results: Array = []; - const FIND_BEGIN_END = /<>(.+?)<>/g; +export const MessageBodyHighlight = (props: Props) => { + const { text } = props; + const results: Array = []; + const FIND_BEGIN_END = /<>(.+?)<>/g; - let match = FIND_BEGIN_END.exec(text); - let last = 0; - let count = 1; + let match = FIND_BEGIN_END.exec(text); + let last = 0; + let count = 1; - if (!match) { - return ; - } - - 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( - - {renderEmoji({ - text: toHighlight, - sizeClass, - key: count++, - renderNonEmoji: renderNewLines, - })} - - ); + if (!match) { + return ; + } - // @ts-ignore - last = FIND_BEGIN_END.lastIndex; - match = FIND_BEGIN_END.exec(text); - } + const sizeClass = ''; - if (last < text.length) { + while (match) { + if (last < match.index) { + const beforeText = text.slice(last, match.index); results.push( renderEmoji({ - text: text.slice(last), + text: beforeText, sizeClass, key: count++, renderNonEmoji: renderNewLines, @@ -85,6 +56,33 @@ export class MessageBodyHighlight extends React.Component { ); } - return results; + const [, toHighlight] = match; + results.push( + + {renderEmoji({ + text: toHighlight, + sizeClass, + key: count++, + renderNonEmoji: renderNewLines, + })} + + ); + + // @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; +}; diff --git a/ts/components/conversation/Emojify.tsx b/ts/components/conversation/Emojify.tsx index 9fa41a70d..5a83619c9 100644 --- a/ts/components/conversation/Emojify.tsx +++ b/ts/components/conversation/Emojify.tsx @@ -43,6 +43,7 @@ export class Emojify extends React.Component { while (match) { if (last < match.index) { const textWithNoEmoji = text.slice(last, match.index); + results.push( renderNonEmoji({ text: textWithNoEmoji, diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index a60dfc8df..221a0e7b7 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -860,8 +860,6 @@ class MessageInner extends React.PureComponent { if (target.className === 'text-selectable' || window.contextMenuShown) { return; } - event.preventDefault(); - event.stopPropagation(); } } diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index d831f2c5b..4465b2208 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -964,6 +964,63 @@ class SessionCompositionBoxInner extends React.Component { 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 }) { const messageBox = this.textarea.current; if (!messageBox) { @@ -973,10 +1030,12 @@ class SessionCompositionBoxInner extends React.Component { const { message } = this.state; const currentSelectionStart = Number(messageBox.selectionStart); - const currentSelectionEnd = Number(messageBox.selectionEnd); - const before = message.slice(0, currentSelectionStart); - const end = message.slice(currentSelectionEnd); + const realSelectionStart = this.getSelectionBasedOnMentions(currentSelectionStart); + + const before = message.slice(0, realSelectionStart); + const end = message.slice(realSelectionStart); + const newMessage = `${before}${colons}${end}`; this.setState({ message: newMessage }, () => { diff --git a/ts/components/session/conversation/SessionEmojiPanel.tsx b/ts/components/session/conversation/SessionEmojiPanel.tsx index 9657c15ca..b9b174712 100644 --- a/ts/components/session/conversation/SessionEmojiPanel.tsx +++ b/ts/components/session/conversation/SessionEmojiPanel.tsx @@ -3,42 +3,27 @@ import classNames from 'classnames'; import { Picker } from 'emoji-mart'; import { Constants } from '../../../session'; -interface Props { +type Props = { onEmojiClicked: (emoji: any) => void; show: boolean; -} +}; -interface State { - // FIXME Use Emoji-Mart categories - category: null; -} +export const SessionEmojiPanel = (props: Props) => { + const { onEmojiClicked, show } = props; -export class SessionEmojiPanel extends React.Component { - constructor(props: Props) { - super(props); - - this.state = { - category: null, - }; - } - - public render() { - const { onEmojiClicked, show } = this.props; - - return ( -
- './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} - /> -
- ); - } -} + return ( +
+ './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} + /> +
+ ); +}; From 1fb3f74f6e192a09a82998968e8cf413e4002eaa Mon Sep 17 00:00:00 2001 From: audric Date: Wed, 4 Aug 2021 15:39:14 +1000 Subject: [PATCH 4/4] disable drag on images --- ts/components/Avatar.tsx | 8 +++++++- ts/components/Lightbox.tsx | 13 +++++++++++-- ts/components/conversation/ConversationHeader.tsx | 4 ++-- ts/components/conversation/Image.tsx | 9 ++++++++- ts/components/conversation/Quote.tsx | 14 ++++++++++++-- .../conversation/media-gallery/MediaGridItem.tsx | 9 ++++++++- 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 4da4d87e9..a06865576 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import classNames from 'classnames'; import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder'; @@ -71,6 +71,11 @@ const AvatarImage = (props: { }) => { const { avatarPath, base64Data, name, imageBroken, handleImageError } = props; + const onDragStart = useCallback((e: any) => { + e.preventDefault(); + return false; + }, []); + if ((!avatarPath && !base64Data) || imageBroken) { return null; } @@ -79,6 +84,7 @@ const AvatarImage = (props: { return ( {window.i18n('contactAvatarAlt', diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx index c022cd58a..542c436d5 100644 --- a/ts/components/Lightbox.tsx +++ b/ts/components/Lightbox.tsx @@ -1,6 +1,6 @@ // tslint:disable:react-a11y-anchors -import React, { useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import is from '@sindresorhus/is'; @@ -211,6 +211,10 @@ export const LightboxObject = ({ const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); + const onDragStart = useCallback((e:any) => { + e.preventDefault(); + return false; }, []); + const playVideo = () => { if (!videoRef) { return; @@ -246,7 +250,12 @@ export const LightboxObject = ({ if (isImageTypeSupported) { return ( - {window.i18n('lightboxImageAlt')} + {window.i18n('lightboxImageAlt')} ); } diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 115d7eb2b..8063c76a6 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -219,6 +219,8 @@ export type ConversationHeaderTitleProps = { const ConversationHeaderTitle = () => { const headerTitleProps = useSelector(getConversationHeaderTitleProps); + const notificationSetting = useSelector(getCurrentNotificationSettingText); + const marginXS = useTheme().common.margins.xs; if (!headerTitleProps) { return null; } @@ -256,12 +258,10 @@ const ConversationHeaderTitle = () => { memberCountText = i18n('members', [count]); } - const notificationSetting = useSelector(getCurrentNotificationSettingText); const notificationSubtitle = notificationSetting ? window.i18n('notificationSubtitle', notificationSetting) : null; const title = profileName || name || phoneNumber; - const marginXS = useTheme().common.margins.xs; return (
diff --git a/ts/components/conversation/Image.tsx b/ts/components/conversation/Image.tsx index 1dd4f9a0e..e1c59b736 100644 --- a/ts/components/conversation/Image.tsx +++ b/ts/components/conversation/Image.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import classNames from 'classnames'; import { Spinner } from '../basic/Spinner'; @@ -57,6 +57,11 @@ export const Image = (props: Props) => { width, } = props; + const onDragStart = useCallback((e: any) => { + e.preventDefault(); + return false; + }, []); + const { caption, pending } = attachment || { caption: null, pending: true }; const canClick = onClick && !pending; const role = canClick ? 'button' : undefined; @@ -106,6 +111,7 @@ export const Image = (props: Props) => { height={height} width={width} src={srcData} + onDragStart={onDragStart} /> )} {caption ? ( @@ -113,6 +119,7 @@ export const Image = (props: Props) => { className="module-image__caption-icon" src="images/caption-shadow.svg" alt={window.i18n('imageCaptionIconAlt')} + onDragStart={onDragStart} /> ) : null}
{ const { loading, urlToLoad } = useEncryptedFileFetch(url, contentType); const srcData = !loading ? urlToLoad : ''; + const onDragStart = useCallback((e:any) => { + e.preventDefault(); + return false; + }, []); + const iconElement = icon ? (
@@ -128,7 +133,12 @@ export const QuoteImage = (props: any) => { return (
- {window.i18n('quoteThumbnailAlt')} + {window.i18n('quoteThumbnailAlt')} {iconElement}
); diff --git a/ts/components/conversation/media-gallery/MediaGridItem.tsx b/ts/components/conversation/media-gallery/MediaGridItem.tsx index 49f722445..9383a3467 100644 --- a/ts/components/conversation/media-gallery/MediaGridItem.tsx +++ b/ts/components/conversation/media-gallery/MediaGridItem.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import classNames from 'classnames'; import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome'; @@ -21,6 +21,11 @@ const MediaGridItemContent = (props: Props) => { const [imageBroken, setImageBroken] = useState(false); 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 const srcData = !loading ? urlToLoad : ''; @@ -52,6 +57,7 @@ const MediaGridItemContent = (props: Props) => { className="module-media-grid-item__image" src={srcData} onError={onImageError} + onDragStart={onDragStart} /> ); } else if (contentType && isVideoTypeSupported(contentType)) { @@ -73,6 +79,7 @@ const MediaGridItemContent = (props: Props) => { className="module-media-grid-item__image" src={srcData} onError={onImageError} + onDragStart={onDragStart} />