Disable pasting in composer when in background

This commit is contained in:
Jamie Kyle 2024-06-13 16:22:07 -07:00 committed by GitHub
parent b315162676
commit 5dcb42f964
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 57 additions and 4 deletions

View file

@ -111,6 +111,7 @@ export type OwnProps = Readonly<{
isGroupV1AndDisabled: boolean | null;
isMissingMandatoryProfileSharing: boolean | null;
isSignalConversation: boolean | null;
isActive: boolean;
lastEditableMessageId: string | null;
recordingState: RecordingState;
messageCompositionId: string;
@ -236,6 +237,7 @@ export const CompositionArea = memo(function CompositionArea({
imageToBlurHash,
isDisabled,
isSignalConversation,
isActive,
lastEditableMessageId,
messageCompositionId,
pushPanelForConversation,
@ -1001,6 +1003,7 @@ export const CompositionArea = memo(function CompositionArea({
i18n={i18n}
inputApi={inputApiRef}
isFormattingEnabled={isFormattingEnabled}
isActive={isActive}
large={large}
linkPreviewLoading={linkPreviewLoading}
linkPreviewResult={linkPreviewResult}

View file

@ -33,6 +33,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => {
clearQuotedMessage: action('clearQuotedMessage'),
getPreferredBadge: () => undefined,
getQuotedMessage: action('getQuotedMessage'),
isActive: true,
isFormattingEnabled:
overrideProps.isFormattingEnabled === false
? overrideProps.isFormattingEnabled

View file

@ -105,6 +105,7 @@ export type Props = Readonly<{
large: boolean | null;
inputApi: React.MutableRefObject<InputApi | undefined> | null;
isFormattingEnabled: boolean;
isActive: boolean;
sendCounter: number;
skinTone: NonNullable<EmojiPickDataType['skinTone']> | null;
draftText: string | null;
@ -158,6 +159,7 @@ export function CompositionInput(props: Props): React.ReactElement {
i18n,
inputApi,
isFormattingEnabled,
isActive,
large,
linkPreviewLoading,
linkPreviewResult,
@ -409,9 +411,14 @@ export function CompositionInput(props: Props): React.ReactElement {
isMouseDown,
previousFormattingEnabled,
previousIsMouseDown,
quillRef,
]);
React.useEffect(() => {
quillRef.current?.getModule('signalClipboard').updateOptions({
isDisabled: !isActive,
});
}, [isActive]);
const onEnter = (): boolean => {
const quill = quillRef.current;
const emojiCompletion = emojiCompletionRef.current;
@ -702,7 +709,9 @@ export function CompositionInput(props: Props): React.ReactElement {
defaultValue={delta}
modules={{
toolbar: false,
signalClipboard: true,
signalClipboard: {
isDisabled: !isActive,
},
clipboard: {
matchers: [
['IMG', matchEmojiImage],

View file

@ -21,6 +21,7 @@ import * as grapheme from '../util/grapheme';
export type CompositionTextAreaProps = {
bodyRanges: HydratedBodyRangesType | null;
i18n: LocalizerType;
isActive: boolean;
isFormattingEnabled: boolean;
maxLength?: number;
placeholder?: string;
@ -58,6 +59,7 @@ export function CompositionTextArea({
draftText,
getPreferredBadge,
i18n,
isActive,
isFormattingEnabled,
maxLength,
onChange,
@ -139,6 +141,7 @@ export function CompositionTextArea({
getPreferredBadge={getPreferredBadge}
getQuotedMessage={noop}
i18n={i18n}
isActive={isActive}
isFormattingEnabled={isFormattingEnabled}
inputApi={inputApiRef}
large

View file

@ -62,6 +62,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
{...props}
getPreferredBadge={() => undefined}
i18n={i18n}
isActive
isFormattingEnabled
onPickEmoji={action('onPickEmoji')}
onSetSkinTone={action('onSetSkinTone')}

View file

@ -492,6 +492,7 @@ function ForwardMessageEditor({
<RenderCompositionTextArea
bodyRanges={draft.bodyRanges ?? null}
draftText={draft.messageBody ?? ''}
isActive
onChange={onChange}
onSubmit={onSubmit}
theme={theme}

View file

@ -1300,6 +1300,7 @@ export function MediaEditor({
getPreferredBadge={getPreferredBadge}
i18n={i18n}
inputApi={inputApiRef}
isActive
isFormattingEnabled={isFormattingEnabled}
moduleClassName="StoryViewsNRepliesModal__input"
onCloseLinkPreview={noop}

View file

@ -236,6 +236,7 @@ export function StoryViewsNRepliesModal({
getPreferredBadge={getPreferredBadge}
i18n={i18n}
inputApi={inputApiRef}
isActive
isFormattingEnabled={isFormattingEnabled}
moduleClassName="StoryViewsNRepliesModal__input"
onCloseLinkPreview={noop}

View file

@ -8,6 +8,7 @@ import { useEscapeHandling } from '../../hooks/useEscapeHandling';
export type PropsType = {
conversationId: string;
hasOpenModal: boolean;
hasOpenPanel: boolean;
isSelectMode: boolean;
onExitSelectMode: () => void;
processAttachments: (options: {
@ -24,6 +25,7 @@ export type PropsType = {
export function ConversationView({
conversationId,
hasOpenModal,
hasOpenPanel,
isSelectMode,
onExitSelectMode,
processAttachments,
@ -57,6 +59,10 @@ export function ConversationView({
const onPaste = React.useCallback(
(event: React.ClipboardEvent<HTMLDivElement>) => {
if (hasOpenModal || hasOpenPanel) {
return;
}
if (!event.clipboardData) {
return;
}
@ -102,7 +108,7 @@ export function ConversationView({
event.preventDefault();
}
},
[conversationId, processAttachments]
[conversationId, processAttachments, hasOpenModal, hasOpenPanel]
);
useEscapeHandling(

View file

@ -19,16 +19,30 @@ const prepareText = (text: string) => {
return `<span>${escapedEntities}</span>`;
};
type ClipboardOptions = Readonly<{
isDisabled: boolean;
}>;
export class SignalClipboard {
quill: Quill;
options: ClipboardOptions;
constructor(quill: Quill) {
constructor(quill: Quill, options: ClipboardOptions) {
this.quill = quill;
this.options = options;
this.quill.root.addEventListener('paste', e => this.onCapturePaste(e));
}
updateOptions(options: Partial<ClipboardOptions>): void {
this.options = { ...this.options, ...options };
}
onCapturePaste(event: ClipboardEvent): void {
if (this.options.isDisabled) {
return;
}
if (event.clipboardData == null) {
event.preventDefault();
event.stopPropagation();

2
ts/quill/types.d.ts vendored
View file

@ -5,6 +5,7 @@ import type UpdatedDelta from 'quill-delta';
import type { MentionCompletion } from './mentions/completion';
import type { EmojiCompletion } from './emoji/completion';
import type { FormattingMenu } from './formatting/menu';
import type { SignalClipboard } from './signal-clipboard';
declare module 'react-quill' {
// `react-quill` uses a different but compatible version of Delta
@ -88,6 +89,7 @@ declare module 'quill' {
getModule(module: 'formattingMenu'): FormattingMenu;
getModule(module: 'history'): HistoryStatic;
getModule(module: 'mentionCompletion'): MentionCompletion;
getModule(module: 'signalClipboard'): SignalClipboard;
getModule(module: string): unknown;
selection: SelectionStatic;

View file

@ -64,6 +64,7 @@ import { useEmojisActions } from '../ducks/emojis';
import { useGlobalModalActions } from '../ducks/globalModals';
import { useStickersActions } from '../ducks/stickers';
import { useToastActions } from '../ducks/toast';
import { isShowingAnyModal } from '../selectors/globalModals';
function renderSmartCompositionRecording(
recProps: SmartCompositionRecordingProps
@ -107,6 +108,8 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
const errorDialogAudioRecorderType = useSelector(
getErrorDialogAudioRecorderType
);
const hasGlobalModalOpen = useSelector(isShowingAnyModal);
const hasPanelOpen = useSelector(getHasPanelOpen);
const getGroupAdmins = useSelector(getGroupAdminsSelector);
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const composerStateForConversationIdSelector = useSelector(
@ -126,6 +129,10 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
shouldSendHighQualityAttachments,
} = composerState;
const isActive = useMemo(() => {
return !hasGlobalModalOpen && !hasPanelOpen;
}, [hasGlobalModalOpen, hasPanelOpen]);
const groupAdmins = useMemo(() => {
return getGroupAdmins(id);
}, [getGroupAdmins, id]);
@ -244,6 +251,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
i18n={i18n}
isDisabled={isDisabled}
isFormattingEnabled={isFormattingEnabled}
isActive={isActive}
lastEditableMessageId={lastEditableMessageId ?? null}
messageCompositionId={messageCompositionId}
platform={platform}

View file

@ -15,6 +15,7 @@ export type SmartCompositionTextAreaProps = Pick<
CompositionTextAreaProps,
| 'bodyRanges'
| 'draftText'
| 'isActive'
| 'placeholder'
| 'onChange'
| 'onScroll'
@ -43,6 +44,7 @@ export const SmartCompositionTextArea = memo(function SmartCompositionTextArea(
{...props}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
isActive
isFormattingEnabled={isFormattingEnabled}
onPickEmoji={onPickEmoji}
onSetSkinTone={onSetSkinTone}

View file

@ -61,6 +61,7 @@ export const SmartConversationView = memo(
<ConversationView
conversationId={conversationId}
hasOpenModal={hasOpenModal}
hasOpenPanel={activePanel != null}
isSelectMode={isSelectMode}
onExitSelectMode={onExitSelectMode}
processAttachments={processAttachments}