Disable pasting in composer when in background
This commit is contained in:
parent
b315162676
commit
5dcb42f964
14 changed files with 57 additions and 4 deletions
|
@ -111,6 +111,7 @@ export type OwnProps = Readonly<{
|
||||||
isGroupV1AndDisabled: boolean | null;
|
isGroupV1AndDisabled: boolean | null;
|
||||||
isMissingMandatoryProfileSharing: boolean | null;
|
isMissingMandatoryProfileSharing: boolean | null;
|
||||||
isSignalConversation: boolean | null;
|
isSignalConversation: boolean | null;
|
||||||
|
isActive: boolean;
|
||||||
lastEditableMessageId: string | null;
|
lastEditableMessageId: string | null;
|
||||||
recordingState: RecordingState;
|
recordingState: RecordingState;
|
||||||
messageCompositionId: string;
|
messageCompositionId: string;
|
||||||
|
@ -236,6 +237,7 @@ export const CompositionArea = memo(function CompositionArea({
|
||||||
imageToBlurHash,
|
imageToBlurHash,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isSignalConversation,
|
isSignalConversation,
|
||||||
|
isActive,
|
||||||
lastEditableMessageId,
|
lastEditableMessageId,
|
||||||
messageCompositionId,
|
messageCompositionId,
|
||||||
pushPanelForConversation,
|
pushPanelForConversation,
|
||||||
|
@ -1001,6 +1003,7 @@ export const CompositionArea = memo(function CompositionArea({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
inputApi={inputApiRef}
|
inputApi={inputApiRef}
|
||||||
isFormattingEnabled={isFormattingEnabled}
|
isFormattingEnabled={isFormattingEnabled}
|
||||||
|
isActive={isActive}
|
||||||
large={large}
|
large={large}
|
||||||
linkPreviewLoading={linkPreviewLoading}
|
linkPreviewLoading={linkPreviewLoading}
|
||||||
linkPreviewResult={linkPreviewResult}
|
linkPreviewResult={linkPreviewResult}
|
||||||
|
|
|
@ -33,6 +33,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => {
|
||||||
clearQuotedMessage: action('clearQuotedMessage'),
|
clearQuotedMessage: action('clearQuotedMessage'),
|
||||||
getPreferredBadge: () => undefined,
|
getPreferredBadge: () => undefined,
|
||||||
getQuotedMessage: action('getQuotedMessage'),
|
getQuotedMessage: action('getQuotedMessage'),
|
||||||
|
isActive: true,
|
||||||
isFormattingEnabled:
|
isFormattingEnabled:
|
||||||
overrideProps.isFormattingEnabled === false
|
overrideProps.isFormattingEnabled === false
|
||||||
? overrideProps.isFormattingEnabled
|
? overrideProps.isFormattingEnabled
|
||||||
|
|
|
@ -105,6 +105,7 @@ export type Props = Readonly<{
|
||||||
large: boolean | null;
|
large: boolean | null;
|
||||||
inputApi: React.MutableRefObject<InputApi | undefined> | null;
|
inputApi: React.MutableRefObject<InputApi | undefined> | null;
|
||||||
isFormattingEnabled: boolean;
|
isFormattingEnabled: boolean;
|
||||||
|
isActive: boolean;
|
||||||
sendCounter: number;
|
sendCounter: number;
|
||||||
skinTone: NonNullable<EmojiPickDataType['skinTone']> | null;
|
skinTone: NonNullable<EmojiPickDataType['skinTone']> | null;
|
||||||
draftText: string | null;
|
draftText: string | null;
|
||||||
|
@ -158,6 +159,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
i18n,
|
i18n,
|
||||||
inputApi,
|
inputApi,
|
||||||
isFormattingEnabled,
|
isFormattingEnabled,
|
||||||
|
isActive,
|
||||||
large,
|
large,
|
||||||
linkPreviewLoading,
|
linkPreviewLoading,
|
||||||
linkPreviewResult,
|
linkPreviewResult,
|
||||||
|
@ -409,9 +411,14 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
isMouseDown,
|
isMouseDown,
|
||||||
previousFormattingEnabled,
|
previousFormattingEnabled,
|
||||||
previousIsMouseDown,
|
previousIsMouseDown,
|
||||||
quillRef,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
quillRef.current?.getModule('signalClipboard').updateOptions({
|
||||||
|
isDisabled: !isActive,
|
||||||
|
});
|
||||||
|
}, [isActive]);
|
||||||
|
|
||||||
const onEnter = (): boolean => {
|
const onEnter = (): boolean => {
|
||||||
const quill = quillRef.current;
|
const quill = quillRef.current;
|
||||||
const emojiCompletion = emojiCompletionRef.current;
|
const emojiCompletion = emojiCompletionRef.current;
|
||||||
|
@ -702,7 +709,9 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
defaultValue={delta}
|
defaultValue={delta}
|
||||||
modules={{
|
modules={{
|
||||||
toolbar: false,
|
toolbar: false,
|
||||||
signalClipboard: true,
|
signalClipboard: {
|
||||||
|
isDisabled: !isActive,
|
||||||
|
},
|
||||||
clipboard: {
|
clipboard: {
|
||||||
matchers: [
|
matchers: [
|
||||||
['IMG', matchEmojiImage],
|
['IMG', matchEmojiImage],
|
||||||
|
|
|
@ -21,6 +21,7 @@ import * as grapheme from '../util/grapheme';
|
||||||
export type CompositionTextAreaProps = {
|
export type CompositionTextAreaProps = {
|
||||||
bodyRanges: HydratedBodyRangesType | null;
|
bodyRanges: HydratedBodyRangesType | null;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
isActive: boolean;
|
||||||
isFormattingEnabled: boolean;
|
isFormattingEnabled: boolean;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -58,6 +59,7 @@ export function CompositionTextArea({
|
||||||
draftText,
|
draftText,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
|
isActive,
|
||||||
isFormattingEnabled,
|
isFormattingEnabled,
|
||||||
maxLength,
|
maxLength,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -139,6 +141,7 @@ export function CompositionTextArea({
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
getQuotedMessage={noop}
|
getQuotedMessage={noop}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isActive={isActive}
|
||||||
isFormattingEnabled={isFormattingEnabled}
|
isFormattingEnabled={isFormattingEnabled}
|
||||||
inputApi={inputApiRef}
|
inputApi={inputApiRef}
|
||||||
large
|
large
|
||||||
|
|
|
@ -62,6 +62,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
{...props}
|
{...props}
|
||||||
getPreferredBadge={() => undefined}
|
getPreferredBadge={() => undefined}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isActive
|
||||||
isFormattingEnabled
|
isFormattingEnabled
|
||||||
onPickEmoji={action('onPickEmoji')}
|
onPickEmoji={action('onPickEmoji')}
|
||||||
onSetSkinTone={action('onSetSkinTone')}
|
onSetSkinTone={action('onSetSkinTone')}
|
||||||
|
|
|
@ -492,6 +492,7 @@ function ForwardMessageEditor({
|
||||||
<RenderCompositionTextArea
|
<RenderCompositionTextArea
|
||||||
bodyRanges={draft.bodyRanges ?? null}
|
bodyRanges={draft.bodyRanges ?? null}
|
||||||
draftText={draft.messageBody ?? ''}
|
draftText={draft.messageBody ?? ''}
|
||||||
|
isActive
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|
|
@ -1300,6 +1300,7 @@ export function MediaEditor({
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
inputApi={inputApiRef}
|
inputApi={inputApiRef}
|
||||||
|
isActive
|
||||||
isFormattingEnabled={isFormattingEnabled}
|
isFormattingEnabled={isFormattingEnabled}
|
||||||
moduleClassName="StoryViewsNRepliesModal__input"
|
moduleClassName="StoryViewsNRepliesModal__input"
|
||||||
onCloseLinkPreview={noop}
|
onCloseLinkPreview={noop}
|
||||||
|
|
|
@ -236,6 +236,7 @@ export function StoryViewsNRepliesModal({
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
inputApi={inputApiRef}
|
inputApi={inputApiRef}
|
||||||
|
isActive
|
||||||
isFormattingEnabled={isFormattingEnabled}
|
isFormattingEnabled={isFormattingEnabled}
|
||||||
moduleClassName="StoryViewsNRepliesModal__input"
|
moduleClassName="StoryViewsNRepliesModal__input"
|
||||||
onCloseLinkPreview={noop}
|
onCloseLinkPreview={noop}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useEscapeHandling } from '../../hooks/useEscapeHandling';
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
hasOpenModal: boolean;
|
hasOpenModal: boolean;
|
||||||
|
hasOpenPanel: boolean;
|
||||||
isSelectMode: boolean;
|
isSelectMode: boolean;
|
||||||
onExitSelectMode: () => void;
|
onExitSelectMode: () => void;
|
||||||
processAttachments: (options: {
|
processAttachments: (options: {
|
||||||
|
@ -24,6 +25,7 @@ export type PropsType = {
|
||||||
export function ConversationView({
|
export function ConversationView({
|
||||||
conversationId,
|
conversationId,
|
||||||
hasOpenModal,
|
hasOpenModal,
|
||||||
|
hasOpenPanel,
|
||||||
isSelectMode,
|
isSelectMode,
|
||||||
onExitSelectMode,
|
onExitSelectMode,
|
||||||
processAttachments,
|
processAttachments,
|
||||||
|
@ -57,6 +59,10 @@ export function ConversationView({
|
||||||
|
|
||||||
const onPaste = React.useCallback(
|
const onPaste = React.useCallback(
|
||||||
(event: React.ClipboardEvent<HTMLDivElement>) => {
|
(event: React.ClipboardEvent<HTMLDivElement>) => {
|
||||||
|
if (hasOpenModal || hasOpenPanel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!event.clipboardData) {
|
if (!event.clipboardData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +108,7 @@ export function ConversationView({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[conversationId, processAttachments]
|
[conversationId, processAttachments, hasOpenModal, hasOpenPanel]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEscapeHandling(
|
useEscapeHandling(
|
||||||
|
|
|
@ -19,16 +19,30 @@ const prepareText = (text: string) => {
|
||||||
return `<span>${escapedEntities}</span>`;
|
return `<span>${escapedEntities}</span>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ClipboardOptions = Readonly<{
|
||||||
|
isDisabled: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
export class SignalClipboard {
|
export class SignalClipboard {
|
||||||
quill: Quill;
|
quill: Quill;
|
||||||
|
options: ClipboardOptions;
|
||||||
|
|
||||||
constructor(quill: Quill) {
|
constructor(quill: Quill, options: ClipboardOptions) {
|
||||||
this.quill = quill;
|
this.quill = quill;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
this.quill.root.addEventListener('paste', e => this.onCapturePaste(e));
|
this.quill.root.addEventListener('paste', e => this.onCapturePaste(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateOptions(options: Partial<ClipboardOptions>): void {
|
||||||
|
this.options = { ...this.options, ...options };
|
||||||
|
}
|
||||||
|
|
||||||
onCapturePaste(event: ClipboardEvent): void {
|
onCapturePaste(event: ClipboardEvent): void {
|
||||||
|
if (this.options.isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.clipboardData == null) {
|
if (event.clipboardData == null) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
2
ts/quill/types.d.ts
vendored
2
ts/quill/types.d.ts
vendored
|
@ -5,6 +5,7 @@ import type UpdatedDelta from 'quill-delta';
|
||||||
import type { MentionCompletion } from './mentions/completion';
|
import type { MentionCompletion } from './mentions/completion';
|
||||||
import type { EmojiCompletion } from './emoji/completion';
|
import type { EmojiCompletion } from './emoji/completion';
|
||||||
import type { FormattingMenu } from './formatting/menu';
|
import type { FormattingMenu } from './formatting/menu';
|
||||||
|
import type { SignalClipboard } from './signal-clipboard';
|
||||||
|
|
||||||
declare module 'react-quill' {
|
declare module 'react-quill' {
|
||||||
// `react-quill` uses a different but compatible version of Delta
|
// `react-quill` uses a different but compatible version of Delta
|
||||||
|
@ -88,6 +89,7 @@ declare module 'quill' {
|
||||||
getModule(module: 'formattingMenu'): FormattingMenu;
|
getModule(module: 'formattingMenu'): FormattingMenu;
|
||||||
getModule(module: 'history'): HistoryStatic;
|
getModule(module: 'history'): HistoryStatic;
|
||||||
getModule(module: 'mentionCompletion'): MentionCompletion;
|
getModule(module: 'mentionCompletion'): MentionCompletion;
|
||||||
|
getModule(module: 'signalClipboard'): SignalClipboard;
|
||||||
getModule(module: string): unknown;
|
getModule(module: string): unknown;
|
||||||
|
|
||||||
selection: SelectionStatic;
|
selection: SelectionStatic;
|
||||||
|
|
|
@ -64,6 +64,7 @@ import { useEmojisActions } from '../ducks/emojis';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useStickersActions } from '../ducks/stickers';
|
import { useStickersActions } from '../ducks/stickers';
|
||||||
import { useToastActions } from '../ducks/toast';
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
import { isShowingAnyModal } from '../selectors/globalModals';
|
||||||
|
|
||||||
function renderSmartCompositionRecording(
|
function renderSmartCompositionRecording(
|
||||||
recProps: SmartCompositionRecordingProps
|
recProps: SmartCompositionRecordingProps
|
||||||
|
@ -107,6 +108,8 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||||
const errorDialogAudioRecorderType = useSelector(
|
const errorDialogAudioRecorderType = useSelector(
|
||||||
getErrorDialogAudioRecorderType
|
getErrorDialogAudioRecorderType
|
||||||
);
|
);
|
||||||
|
const hasGlobalModalOpen = useSelector(isShowingAnyModal);
|
||||||
|
const hasPanelOpen = useSelector(getHasPanelOpen);
|
||||||
const getGroupAdmins = useSelector(getGroupAdminsSelector);
|
const getGroupAdmins = useSelector(getGroupAdminsSelector);
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
const composerStateForConversationIdSelector = useSelector(
|
const composerStateForConversationIdSelector = useSelector(
|
||||||
|
@ -126,6 +129,10 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||||
shouldSendHighQualityAttachments,
|
shouldSendHighQualityAttachments,
|
||||||
} = composerState;
|
} = composerState;
|
||||||
|
|
||||||
|
const isActive = useMemo(() => {
|
||||||
|
return !hasGlobalModalOpen && !hasPanelOpen;
|
||||||
|
}, [hasGlobalModalOpen, hasPanelOpen]);
|
||||||
|
|
||||||
const groupAdmins = useMemo(() => {
|
const groupAdmins = useMemo(() => {
|
||||||
return getGroupAdmins(id);
|
return getGroupAdmins(id);
|
||||||
}, [getGroupAdmins, id]);
|
}, [getGroupAdmins, id]);
|
||||||
|
@ -244,6 +251,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isFormattingEnabled={isFormattingEnabled}
|
isFormattingEnabled={isFormattingEnabled}
|
||||||
|
isActive={isActive}
|
||||||
lastEditableMessageId={lastEditableMessageId ?? null}
|
lastEditableMessageId={lastEditableMessageId ?? null}
|
||||||
messageCompositionId={messageCompositionId}
|
messageCompositionId={messageCompositionId}
|
||||||
platform={platform}
|
platform={platform}
|
||||||
|
|
|
@ -15,6 +15,7 @@ export type SmartCompositionTextAreaProps = Pick<
|
||||||
CompositionTextAreaProps,
|
CompositionTextAreaProps,
|
||||||
| 'bodyRanges'
|
| 'bodyRanges'
|
||||||
| 'draftText'
|
| 'draftText'
|
||||||
|
| 'isActive'
|
||||||
| 'placeholder'
|
| 'placeholder'
|
||||||
| 'onChange'
|
| 'onChange'
|
||||||
| 'onScroll'
|
| 'onScroll'
|
||||||
|
@ -43,6 +44,7 @@ export const SmartCompositionTextArea = memo(function SmartCompositionTextArea(
|
||||||
{...props}
|
{...props}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isActive
|
||||||
isFormattingEnabled={isFormattingEnabled}
|
isFormattingEnabled={isFormattingEnabled}
|
||||||
onPickEmoji={onPickEmoji}
|
onPickEmoji={onPickEmoji}
|
||||||
onSetSkinTone={onSetSkinTone}
|
onSetSkinTone={onSetSkinTone}
|
||||||
|
|
|
@ -61,6 +61,7 @@ export const SmartConversationView = memo(
|
||||||
<ConversationView
|
<ConversationView
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
hasOpenModal={hasOpenModal}
|
hasOpenModal={hasOpenModal}
|
||||||
|
hasOpenPanel={activePanel != null}
|
||||||
isSelectMode={isSelectMode}
|
isSelectMode={isSelectMode}
|
||||||
onExitSelectMode={onExitSelectMode}
|
onExitSelectMode={onExitSelectMode}
|
||||||
processAttachments={processAttachments}
|
processAttachments={processAttachments}
|
||||||
|
|
Loading…
Reference in a new issue