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;
|
||||
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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -62,6 +62,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
{...props}
|
||||
getPreferredBadge={() => undefined}
|
||||
i18n={i18n}
|
||||
isActive
|
||||
isFormattingEnabled
|
||||
onPickEmoji={action('onPickEmoji')}
|
||||
onSetSkinTone={action('onSetSkinTone')}
|
||||
|
|
|
@ -492,6 +492,7 @@ function ForwardMessageEditor({
|
|||
<RenderCompositionTextArea
|
||||
bodyRanges={draft.bodyRanges ?? null}
|
||||
draftText={draft.messageBody ?? ''}
|
||||
isActive
|
||||
onChange={onChange}
|
||||
onSubmit={onSubmit}
|
||||
theme={theme}
|
||||
|
|
|
@ -1300,6 +1300,7 @@ export function MediaEditor({
|
|||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
inputApi={inputApiRef}
|
||||
isActive
|
||||
isFormattingEnabled={isFormattingEnabled}
|
||||
moduleClassName="StoryViewsNRepliesModal__input"
|
||||
onCloseLinkPreview={noop}
|
||||
|
|
|
@ -236,6 +236,7 @@ export function StoryViewsNRepliesModal({
|
|||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
inputApi={inputApiRef}
|
||||
isActive
|
||||
isFormattingEnabled={isFormattingEnabled}
|
||||
moduleClassName="StoryViewsNRepliesModal__input"
|
||||
onCloseLinkPreview={noop}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
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 { 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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -61,6 +61,7 @@ export const SmartConversationView = memo(
|
|||
<ConversationView
|
||||
conversationId={conversationId}
|
||||
hasOpenModal={hasOpenModal}
|
||||
hasOpenPanel={activePanel != null}
|
||||
isSelectMode={isSelectMode}
|
||||
onExitSelectMode={onExitSelectMode}
|
||||
processAttachments={processAttachments}
|
||||
|
|
Loading…
Reference in a new issue