+ {/* FocusTrap needs a button always mounted as a start, which is apparently not our case */}
+
+
+
+ {showHeader ? (
+
+
+ {showExitIcon ? (
+
+ ) : null}
+
+
{title}
+
+ {headerIconButtons
+ ? headerIconButtons.map((iconItem: any) => {
+ return (
+
+ );
+ })
+ : null}
+
-
{title}
-
- {headerIconButtons
- ? headerIconButtons.map((iconItem: any) => {
- return (
-
- );
- })
- : null}
-
-
- ) : null}
+ ) : null}
-
-
- {props.children}
+
+
+ {props.children}
-
- {onConfirm ? (
-
- {confirmText || window.i18n('ok')}
-
- ) : null}
- {onClose && showClose ? (
-
- {cancelText || window.i18n('close')}
-
- ) : null}
+
+ {onConfirm ? (
+
+ {confirmText || window.i18n('ok')}
+
+ ) : null}
+ {onClose && showClose ? (
+
+ {cancelText || window.i18n('close')}
+
+ ) : null}
+
-
+
);
};
diff --git a/ts/components/basic/SessionButton.tsx b/ts/components/basic/SessionButton.tsx
index 10a1d33e8..e996ab350 100644
--- a/ts/components/basic/SessionButton.tsx
+++ b/ts/components/basic/SessionButton.tsx
@@ -1,5 +1,5 @@
-import React, { ReactNode } from 'react';
import classNames from 'classnames';
+import React, { ReactNode } from 'react';
import styled from 'styled-components';
export enum SessionButtonType {
@@ -28,7 +28,7 @@ export enum SessionButtonColor {
None = 'transparent',
}
-const StyledButton = styled.div<{
+const StyledButton = styled.button<{
color: string | undefined;
buttonType: SessionButtonType;
buttonShape: SessionButtonShape;
@@ -67,6 +67,10 @@ const StyledButton = styled.div<{
'box-shadow: 0px 0px 6px var(--button-solid-shadow-color);'}
border-radius: ${props => (props.buttonShape === SessionButtonShape.Round ? '17px' : '6px')};
+ :focus-within {
+ outline: 1px var(--primary-color) dashed;
+ }
+
.session-icon {
fill: var(--background-primary-color);
}
diff --git a/ts/components/basic/SessionRadio.tsx b/ts/components/basic/SessionRadio.tsx
index 56a5b41b2..ca4302d15 100644
--- a/ts/components/basic/SessionRadio.tsx
+++ b/ts/components/basic/SessionRadio.tsx
@@ -21,6 +21,10 @@ const StyledInput = styled.input<{
? props.selectedColor
: 'var(--primary-color)'};
}
+
+ :focus-within + label:before {
+ outline: 1px var(--primary-color) dashed;
+ }
`;
// NOTE (Will): We don't use a transition because it's too slow and creates flickering when changing buttons.
diff --git a/ts/components/conversation/SessionConversation.tsx b/ts/components/conversation/SessionConversation.tsx
index bdf4a06d3..a7cddf9db 100644
--- a/ts/components/conversation/SessionConversation.tsx
+++ b/ts/components/conversation/SessionConversation.tsx
@@ -53,10 +53,7 @@ import { LightboxGallery, MediaItemType } from '../lightbox/LightboxGallery';
import { NoMessageInConversation } from './SubtleNotification';
import { ConversationHeaderWithDetails } from './header/ConversationHeader';
-import {
- deleteMessagesById,
- deleteMessagesByIdForEveryone,
-} from '../../interactions/conversations/unsendingInteractions';
+import { deleteMessagesForX } from '../../interactions/conversations/unsendingInteractions';
import { isAudio } from '../../types/MIME';
import { HTMLDirection } from '../../util/i18n';
import { NoticeBanner } from '../NoticeBanner';
@@ -361,14 +358,7 @@ export class SessionConversation extends React.Component
{
case 'Backspace':
case 'Delete':
if (selectionMode && this.props.selectedConversationKey) {
- if (isPublic) {
- void deleteMessagesByIdForEveryone(selectedMessages, selectedConversationKey);
- } else {
- void deleteMessagesById(
- this.props.selectedMessages,
- this.props.selectedConversationKey
- );
- }
+ void deleteMessagesForX(selectedMessages, selectedConversationKey, isPublic);
}
break;
default:
diff --git a/ts/components/conversation/composition/CompositionButtons.tsx b/ts/components/conversation/composition/CompositionButtons.tsx
index f88cdde7a..8a2a4d665 100644
--- a/ts/components/conversation/composition/CompositionButtons.tsx
+++ b/ts/components/conversation/composition/CompositionButtons.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import styled from 'styled-components';
-import { SessionIconButton } from '../../icon';
import { Noop } from '../../../types/Util';
+import { SessionIconButton } from '../../icon';
const StyledChatButtonContainer = styled.div`
.session-icon-button {
@@ -50,7 +50,7 @@ export const StartRecordingButton = (props: { onClick: Noop }) => {
};
// eslint-disable-next-line react/display-name
-export const ToggleEmojiButton = React.forwardRef(
+export const ToggleEmojiButton = React.forwardRef(
(props, ref) => {
return (
diff --git a/ts/components/conversation/header/ConversationHeaderSelectionOverlay.tsx b/ts/components/conversation/header/ConversationHeaderSelectionOverlay.tsx
index 1ca14e0e5..b96420687 100644
--- a/ts/components/conversation/header/ConversationHeaderSelectionOverlay.tsx
+++ b/ts/components/conversation/header/ConversationHeaderSelectionOverlay.tsx
@@ -1,8 +1,10 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
+import { useKey } from 'react-use';
import {
deleteMessagesById,
deleteMessagesByIdForEveryone,
+ deleteMessagesForX,
} from '../../../interactions/conversations/unsendingInteractions';
import { resetSelectedMessageIds } from '../../../state/ducks/conversations';
import { getSelectedMessageIds } from '../../../state/selectors/conversations';
@@ -33,6 +35,25 @@ export const SelectionOverlay = () => {
const isPublic = useSelectedIsPublic();
const dispatch = useDispatch();
+ useKey('Delete', event => {
+ const selectionMode = !!selectedMessageIds.length;
+
+ switch (event.key) {
+ case 'Escape':
+ if (selectionMode) {
+ dispatch(resetSelectedMessageIds());
+ }
+ break;
+ case 'Backspace':
+ case 'Delete':
+ if (selectionMode && selectedConversationKey) {
+ void deleteMessagesForX(selectedMessageIds, selectedConversationKey, isPublic);
+ }
+ break;
+ default:
+ }
+ });
+
function onCloseOverlay() {
dispatch(resetSelectedMessageIds());
}
diff --git a/ts/components/conversation/message/message-content/MessageContextMenu.tsx b/ts/components/conversation/message/message-content/MessageContextMenu.tsx
index 3b7d8927b..78f46450b 100644
--- a/ts/components/conversation/message/message-content/MessageContextMenu.tsx
+++ b/ts/components/conversation/message/message-content/MessageContextMenu.tsx
@@ -10,10 +10,7 @@ import { Data } from '../../../../data/data';
import { MessageInteraction } from '../../../../interactions';
import { replyToMessage } from '../../../../interactions/conversationInteractions';
-import {
- deleteMessagesById,
- deleteMessagesByIdForEveryone,
-} from '../../../../interactions/conversations/unsendingInteractions';
+import { deleteMessagesForX } from '../../../../interactions/conversations/unsendingInteractions';
import {
addSenderAsModerator,
removeSenderFromModerator,
@@ -97,14 +94,9 @@ const DeleteItem = ({ messageId }: { messageId: string }) => {
const onDelete = useCallback(() => {
if (convoId) {
- if (!isPublic && isDeletable) {
- void deleteMessagesById([messageId], convoId);
- }
- if (isPublic && isDeletableForEveryone) {
- void deleteMessagesByIdForEveryone([messageId], convoId);
- }
+ void deleteMessagesForX([messageId], convoId, isPublic);
}
- }, [convoId, isDeletable, isDeletableForEveryone, isPublic, messageId]);
+ }, [convoId, isPublic, messageId]);
if (!convoId || (isPublic && !isDeletableForEveryone) || (!isPublic && !isDeletable)) {
return null;
diff --git a/ts/components/conversation/right-panel/RightPanel.tsx b/ts/components/conversation/right-panel/RightPanel.tsx
index 8ca45ab0c..d3b28dc10 100644
--- a/ts/components/conversation/right-panel/RightPanel.tsx
+++ b/ts/components/conversation/right-panel/RightPanel.tsx
@@ -21,11 +21,13 @@ export const StyledRightPanelContainer = styled.div`
background-color: var(--background-primary-color);
border-left: 1px solid var(--border-color);
+ visibility: hidden;
&.show {
transform: none;
transition: transform 0.3s ease-in-out;
z-index: 3;
+ visibility: visible;
}
`;
diff --git a/ts/components/dialog/SessionConfirm.tsx b/ts/components/dialog/SessionConfirm.tsx
index a6cf237b4..229a905ff 100644
--- a/ts/components/dialog/SessionConfirm.tsx
+++ b/ts/components/dialog/SessionConfirm.tsx
@@ -1,8 +1,8 @@
import { shell } from 'electron';
import React, { Dispatch, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
-import styled from 'styled-components';
import useKey from 'react-use/lib/useKey';
+import styled from 'styled-components';
import { useLastMessage } from '../../hooks/useParamSelector';
import { MessageInteraction } from '../../interactions';
import {
diff --git a/ts/components/icon/SessionIconButton.tsx b/ts/components/icon/SessionIconButton.tsx
index b572f6619..2370da9d8 100644
--- a/ts/components/icon/SessionIconButton.tsx
+++ b/ts/components/icon/SessionIconButton.tsx
@@ -1,13 +1,13 @@
-import React, { KeyboardEvent } from 'react';
import classNames from 'classnames';
import _ from 'lodash';
+import React, { KeyboardEvent } from 'react';
import styled from 'styled-components';
import { SessionIcon, SessionIconProps } from '.';
import { SessionNotificationCount } from './SessionNotificationCount';
interface SProps extends SessionIconProps {
- onClick?: (e?: React.MouseEvent) => void;
+ onClick?: (e?: React.MouseEvent) => void;
notificationCount?: number;
isSelected?: boolean;
isHidden?: boolean;
@@ -19,7 +19,7 @@ interface SProps extends SessionIconProps {
tabIndex?: number;
}
-const StyledSessionIconButton = styled.div<{ color?: string; isSelected?: boolean }>`
+const StyledSessionIconButton = styled.button<{ color?: string; isSelected?: boolean }>`
background-color: var(--button-icon-background-color);
svg path {
@@ -37,10 +37,14 @@ const StyledSessionIconButton = styled.div<{ color?: string; isSelected?: boolea
&:hover svg path {
${props => !props.color && 'fill: var(--button-icon-stroke-hover-color);'}
}
+
+ :focus-within {
+ outline: 1px var(--primary-color) dashed;
+ }
`;
// eslint-disable-next-line react/display-name
-const SessionIconButtonInner = React.forwardRef((props, ref) => {
+const SessionIconButtonInner = React.forwardRef((props, ref) => {
const {
iconType,
iconSize,
@@ -62,13 +66,13 @@ const SessionIconButtonInner = React.forwardRef((props,
style,
tabIndex,
} = props;
- const clickHandler = (e: React.MouseEvent) => {
+ const clickHandler = (e: React.MouseEvent) => {
if (props.onClick) {
e.stopPropagation();
props.onClick(e);
}
};
- const keyPressHandler = (e: KeyboardEvent) => {
+ const keyPressHandler = (e: KeyboardEvent) => {
if (e.currentTarget.tabIndex > -1 && e.key === 'Enter' && props.onClick) {
e.stopPropagation();
props.onClick();
@@ -80,7 +84,6 @@ const SessionIconButtonInner = React.forwardRef((props,
color={iconColor}
isSelected={isSelected}
className={classNames('session-icon-button', iconSize)}
- role="button"
ref={ref}
id={id}
onClick={clickHandler}
diff --git a/ts/interactions/conversations/unsendingInteractions.ts b/ts/interactions/conversations/unsendingInteractions.ts
index bb4b6e043..16e33e1a7 100644
--- a/ts/interactions/conversations/unsendingInteractions.ts
+++ b/ts/interactions/conversations/unsendingInteractions.ts
@@ -335,6 +335,24 @@ const doDeleteSelectedMessages = async ({
// #endregion
};
+/**
+ * Either delete for everyone or not, based on the props
+ */
+export async function deleteMessagesForX(
+ messageIds: Array,
+ conversationId: string,
+ isPublic: boolean
+) {
+ if (conversationId) {
+ if (!isPublic) {
+ void deleteMessagesById(messageIds, conversationId);
+ }
+ if (isPublic) {
+ void deleteMessagesByIdForEveryone(messageIds, conversationId);
+ }
+ }
+}
+
export async function deleteMessagesByIdForEveryone(
messageIds: Array,
conversationId: string
diff --git a/yarn.lock b/yarn.lock
index 6ea5b2a81..27984f856 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3829,6 +3829,21 @@ flatted@^3.2.7:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
+focus-trap-react@^10.2.3:
+ version "10.2.3"
+ resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-10.2.3.tgz#a5a2ea7fbb042ffa4337fde72758325ed0fb793a"
+ integrity sha512-YXBpFu/hIeSu6NnmV2xlXzOYxuWkoOtar9jzgp3lOmjWLWY59C/b8DtDHEAV4SPU07Nd/t+nS/SBNGkhUBFmEw==
+ dependencies:
+ focus-trap "^7.5.4"
+ tabbable "^6.2.0"
+
+focus-trap@^7.5.4:
+ version "7.5.4"
+ resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.5.4.tgz#6c4e342fe1dae6add9c2aa332a6e7a0bbd495ba2"
+ integrity sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==
+ dependencies:
+ tabbable "^6.2.0"
+
follow-redirects@^1.15.0:
version "1.15.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
@@ -7514,6 +7529,11 @@ symbol-tree@^3.2.4:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+tabbable@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
+ integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
+
tapable@^2.1.1, tapable@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"