Eliminate resetEmojiResults, move onEditorStateChanged to redux
This commit is contained in:
parent
6d868030ae
commit
5c059c54d5
13 changed files with 162 additions and 155 deletions
|
@ -1506,7 +1506,7 @@ export async function startApp(): Promise<void> {
|
|||
shiftKey &&
|
||||
(key === 't' || key === 'T')
|
||||
) {
|
||||
conversation.trigger('focus-composer');
|
||||
window.reduxActions.composer.setComposerFocus(conversation.id);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
|
|
|
@ -34,6 +34,7 @@ export default {
|
|||
const useProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||
addAttachment: action('addAttachment'),
|
||||
conversationId: '123',
|
||||
focusCounter: 0,
|
||||
i18n,
|
||||
isDisabled: false,
|
||||
messageCompositionId: '456',
|
||||
|
@ -41,6 +42,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
processAttachments: action('processAttachments'),
|
||||
removeAttachment: action('removeAttachment'),
|
||||
theme: React.useContext(StorybookThemeContext),
|
||||
setComposerFocus: action('setComposerFocus'),
|
||||
|
||||
// AttachmentList
|
||||
draftAttachments: overrideProps.draftAttachments || [],
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2019-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MutableRefObject } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
@ -60,14 +59,6 @@ import { isImageTypeSupported } from '../util/GoogleChrome';
|
|||
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||
import { usePrevious } from '../hooks/usePrevious';
|
||||
|
||||
export type CompositionAPIType =
|
||||
| {
|
||||
focusInput: () => void;
|
||||
isDirty: () => boolean;
|
||||
resetEmojiResults: InputApi['resetEmojiResults'];
|
||||
}
|
||||
| undefined;
|
||||
|
||||
export type OwnProps = Readonly<{
|
||||
acceptedMessageRequest?: boolean;
|
||||
addAttachment: (
|
||||
|
@ -83,12 +74,12 @@ export type OwnProps = Readonly<{
|
|||
conversationId: string,
|
||||
onSendAudioRecording?: (rec: InMemoryAttachmentDraftType) => unknown
|
||||
) => unknown;
|
||||
compositionApi?: MutableRefObject<CompositionAPIType>;
|
||||
conversationId: string;
|
||||
uuid?: string;
|
||||
draftAttachments: ReadonlyArray<AttachmentDraftType>;
|
||||
errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType;
|
||||
errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
|
||||
focusCounter: number;
|
||||
groupAdmins: Array<ConversationType>;
|
||||
groupVersion?: 1 | 2;
|
||||
i18n: LocalizerType;
|
||||
|
@ -133,6 +124,7 @@ export type OwnProps = Readonly<{
|
|||
'i18n' | 'onClick' | 'onClose' | 'withContentAbove'
|
||||
>;
|
||||
removeAttachment: (conversationId: string, filePath: string) => unknown;
|
||||
setComposerFocus: (conversationId: string) => unknown;
|
||||
setQuotedMessage(message: undefined): unknown;
|
||||
shouldSendHighQualityAttachments: boolean;
|
||||
startRecording: () => unknown;
|
||||
|
@ -177,14 +169,16 @@ export function CompositionArea({
|
|||
// Base props
|
||||
addAttachment,
|
||||
conversationId,
|
||||
focusCounter,
|
||||
i18n,
|
||||
imageToBlurHash,
|
||||
isDisabled,
|
||||
isSignalConversation,
|
||||
messageCompositionId,
|
||||
processAttachments,
|
||||
removeAttachment,
|
||||
messageCompositionId,
|
||||
sendMultiMediaMessage,
|
||||
setComposerFocus,
|
||||
theme,
|
||||
|
||||
// AttachmentList
|
||||
|
@ -209,7 +203,6 @@ export function CompositionArea({
|
|||
onSelectMediaQuality,
|
||||
shouldSendHighQualityAttachments,
|
||||
// CompositionInput
|
||||
compositionApi,
|
||||
onEditorStateChange,
|
||||
onTextTooLong,
|
||||
draftText,
|
||||
|
@ -315,11 +308,22 @@ export function CompositionArea({
|
|||
const attachFileShortcut = useAttachFileShortcut(launchAttachmentPicker);
|
||||
useKeyboardShortcuts(attachFileShortcut);
|
||||
|
||||
const focusInput = useCallback(() => {
|
||||
// Focus input on first mount
|
||||
const previousFocusCounter = usePrevious<number | undefined>(
|
||||
focusCounter,
|
||||
focusCounter
|
||||
);
|
||||
useEffect(() => {
|
||||
if (inputApiRef.current) {
|
||||
inputApiRef.current.focus();
|
||||
}
|
||||
}, [inputApiRef]);
|
||||
});
|
||||
// Focus input whenever explicitly requested
|
||||
useEffect(() => {
|
||||
if (focusCounter !== previousFocusCounter && inputApiRef.current) {
|
||||
inputApiRef.current.focus();
|
||||
}
|
||||
}, [inputApiRef, focusCounter, previousFocusCounter]);
|
||||
|
||||
const withStickers =
|
||||
countStickers({
|
||||
|
@ -329,20 +333,6 @@ export function CompositionArea({
|
|||
receivedPacks,
|
||||
}) > 0;
|
||||
|
||||
if (compositionApi) {
|
||||
// Using a React.MutableRefObject, so we need to reassign this prop.
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
compositionApi.current = {
|
||||
isDirty: () => dirty,
|
||||
focusInput,
|
||||
resetEmojiResults: () => {
|
||||
if (inputApiRef.current) {
|
||||
inputApiRef.current.resetEmojiResults();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const previousMessageCompositionId = usePrevious(
|
||||
messageCompositionId,
|
||||
messageCompositionId
|
||||
|
@ -382,7 +372,7 @@ export function CompositionArea({
|
|||
i18n={i18n}
|
||||
doSend={handleForceSend}
|
||||
onPickEmoji={insertEmoji}
|
||||
onClose={focusInput}
|
||||
onClose={() => setComposerFocus(conversationId)}
|
||||
recentEmojis={recentEmojis}
|
||||
skinTone={skinTone}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
|
@ -706,6 +696,7 @@ export function CompositionArea({
|
|||
)}
|
||||
>
|
||||
<CompositionInput
|
||||
conversationId={conversationId}
|
||||
clearQuotedMessage={clearQuotedMessage}
|
||||
disabled={isDisabled}
|
||||
draftBodyRanges={draftBodyRanges}
|
||||
|
|
|
@ -62,12 +62,12 @@ export type InputApi = {
|
|||
insertEmoji: (e: EmojiPickDataType) => void;
|
||||
setText: (text: string, cursorToEnd?: boolean) => void;
|
||||
reset: () => void;
|
||||
resetEmojiResults: () => void;
|
||||
submit: () => void;
|
||||
};
|
||||
|
||||
export type Props = Readonly<{
|
||||
children?: React.ReactNode;
|
||||
conversationId?: string;
|
||||
i18n: LocalizerType;
|
||||
disabled?: boolean;
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
|
@ -83,6 +83,7 @@ export type Props = Readonly<{
|
|||
scrollerRef?: React.RefObject<HTMLDivElement>;
|
||||
onDirtyChange?(dirty: boolean): unknown;
|
||||
onEditorStateChange?(
|
||||
conversationId: string | undefined,
|
||||
messageText: string,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
|
@ -105,6 +106,7 @@ const BASE_CLASS_NAME = 'module-composition-input';
|
|||
export function CompositionInput(props: Props): React.ReactElement {
|
||||
const {
|
||||
children,
|
||||
conversationId,
|
||||
i18n,
|
||||
disabled,
|
||||
large,
|
||||
|
@ -246,16 +248,6 @@ export function CompositionInput(props: Props): React.ReactElement {
|
|||
}
|
||||
};
|
||||
|
||||
const resetEmojiResults = () => {
|
||||
const emojiCompletion = emojiCompletionRef.current;
|
||||
|
||||
if (emojiCompletion === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
emojiCompletion.reset();
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
const timestamp = Date.now();
|
||||
const quill = quillRef.current;
|
||||
|
@ -286,7 +278,6 @@ export function CompositionInput(props: Props): React.ReactElement {
|
|||
insertEmoji,
|
||||
setText,
|
||||
reset,
|
||||
resetEmojiResults,
|
||||
submit,
|
||||
};
|
||||
}
|
||||
|
@ -446,6 +437,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
|||
const selection = quill.getSelection();
|
||||
|
||||
onEditorStateChange(
|
||||
conversationId,
|
||||
text,
|
||||
mentions,
|
||||
selection ? selection.index : undefined
|
||||
|
|
|
@ -87,6 +87,7 @@ export function CompositionTextArea({
|
|||
|
||||
const handleChange = React.useCallback(
|
||||
(
|
||||
_conversationId: string | undefined,
|
||||
newValue: string,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number | undefined
|
||||
|
|
|
@ -56,6 +56,7 @@ export type DataPropsType = {
|
|||
messageBody?: string;
|
||||
onClose: () => void;
|
||||
onEditorStateChange: (
|
||||
conversationId: string | undefined,
|
||||
messageText: string,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
|
@ -332,7 +333,12 @@ export function ForwardMessageModal({
|
|||
draftText={messageBodyText}
|
||||
onChange={(messageText, bodyRanges, caretLocation?) => {
|
||||
setMessageBodyText(messageText);
|
||||
onEditorStateChange(messageText, bodyRanges, caretLocation);
|
||||
onEditorStateChange(
|
||||
undefined,
|
||||
messageText,
|
||||
bodyRanges,
|
||||
caretLocation
|
||||
);
|
||||
}}
|
||||
onSubmit={forwardMessage}
|
||||
theme={theme}
|
||||
|
|
|
@ -245,7 +245,7 @@ export function StoryViewsNRepliesModal({
|
|||
i18n={i18n}
|
||||
inputApi={inputApiRef}
|
||||
moduleClassName="StoryViewsNRepliesModal__input"
|
||||
onEditorStateChange={messageText => {
|
||||
onEditorStateChange={(_conversationId, messageText) => {
|
||||
setMessageBodyText(messageText);
|
||||
}}
|
||||
onPickEmoji={onUseEmoji}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import path from 'path';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
import type { ThunkAction } from 'redux-thunk';
|
||||
|
||||
import type {
|
||||
|
@ -40,8 +41,9 @@ import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUnti
|
|||
import { clearConversationDraftAttachments } from '../../util/clearConversationDraftAttachments';
|
||||
import { deleteDraftAttachment } from '../../util/deleteDraftAttachment';
|
||||
import {
|
||||
hasLinkPreviewLoaded,
|
||||
getLinkPreviewForSend,
|
||||
hasLinkPreviewLoaded,
|
||||
maybeGrabLinkPreview,
|
||||
resetLinkPreview,
|
||||
} from '../../services/LinkPreview';
|
||||
import { getMaximumAttachmentSize } from '../../util/attachments';
|
||||
|
@ -64,6 +66,7 @@ import { writeDraftAttachment } from '../../util/writeDraftAttachment';
|
|||
|
||||
export type ComposerStateType = {
|
||||
attachments: ReadonlyArray<AttachmentDraftType>;
|
||||
focusCounter: number;
|
||||
isDisabled: boolean;
|
||||
linkPreviewLoading: boolean;
|
||||
linkPreviewResult?: LinkPreviewType;
|
||||
|
@ -77,6 +80,7 @@ export type ComposerStateType = {
|
|||
const ADD_PENDING_ATTACHMENT = 'composer/ADD_PENDING_ATTACHMENT';
|
||||
const REPLACE_ATTACHMENTS = 'composer/REPLACE_ATTACHMENTS';
|
||||
const RESET_COMPOSER = 'composer/RESET_COMPOSER';
|
||||
const SET_FOCUS = 'composer/SET_FOCUS';
|
||||
const SET_HIGH_QUALITY_SETTING = 'composer/SET_HIGH_QUALITY_SETTING';
|
||||
const SET_QUOTED_MESSAGE = 'composer/SET_QUOTED_MESSAGE';
|
||||
const SET_COMPOSER_DISABLED = 'composer/SET_COMPOSER_DISABLED';
|
||||
|
@ -100,6 +104,10 @@ type SetComposerDisabledStateActionType = {
|
|||
payload: boolean;
|
||||
};
|
||||
|
||||
type SetFocusActionType = {
|
||||
type: typeof SET_FOCUS;
|
||||
};
|
||||
|
||||
type SetHighQualitySettingActionType = {
|
||||
type: typeof SET_HIGH_QUALITY_SETTING;
|
||||
payload: boolean;
|
||||
|
@ -117,6 +125,7 @@ type ComposerActionType =
|
|||
| ReplaceAttachmentsActionType
|
||||
| ResetComposerActionType
|
||||
| SetComposerDisabledStateActionType
|
||||
| SetFocusActionType
|
||||
| SetHighQualitySettingActionType
|
||||
| SetQuotedMessageActionType;
|
||||
|
||||
|
@ -125,13 +134,15 @@ type ComposerActionType =
|
|||
export const actions = {
|
||||
addAttachment,
|
||||
addPendingAttachment,
|
||||
onEditorStateChange,
|
||||
processAttachments,
|
||||
removeAttachment,
|
||||
replaceAttachments,
|
||||
resetComposer,
|
||||
setComposerDisabledState,
|
||||
sendMultiMediaMessage,
|
||||
sendStickerMessage,
|
||||
setComposerDisabledState,
|
||||
setComposerFocus,
|
||||
setMediaQualitySetting,
|
||||
setQuotedMessage,
|
||||
};
|
||||
|
@ -421,6 +432,56 @@ function addPendingAttachment(
|
|||
};
|
||||
}
|
||||
|
||||
function setComposerFocus(
|
||||
conversationId: string
|
||||
): ThunkAction<void, RootStateType, unknown, SetFocusActionType> {
|
||||
return async (dispatch, getState) => {
|
||||
if (getState().conversations.selectedConversationId !== conversationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_FOCUS,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function onEditorStateChange(
|
||||
conversationId: string | undefined,
|
||||
messageText: string,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
): NoopActionType {
|
||||
if (!conversationId) {
|
||||
throw new Error(
|
||||
'onEditorStateChange: Got falsey conversationId, needs local override'
|
||||
);
|
||||
}
|
||||
|
||||
const conversation = window.ConversationController.get(conversationId);
|
||||
if (!conversation) {
|
||||
throw new Error('processAttachments: Unable to find conversation');
|
||||
}
|
||||
|
||||
if (messageText.length && conversation.throttledBumpTyping) {
|
||||
conversation.throttledBumpTyping();
|
||||
}
|
||||
|
||||
debouncedSaveDraft(conversationId, messageText, bodyRanges);
|
||||
|
||||
// If we have attachments, don't add link preview
|
||||
if (!hasDraftAttachments(conversation.attributes, { includePending: true })) {
|
||||
maybeGrabLinkPreview(messageText, LinkPreviewSourceType.Composer, {
|
||||
caretLocation,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'NOOP',
|
||||
payload: null,
|
||||
};
|
||||
}
|
||||
|
||||
function processAttachments({
|
||||
conversationId,
|
||||
files,
|
||||
|
@ -661,6 +722,51 @@ function resetComposer(): ResetComposerActionType {
|
|||
type: RESET_COMPOSER,
|
||||
};
|
||||
}
|
||||
const debouncedSaveDraft = debounce(saveDraft);
|
||||
|
||||
function saveDraft(
|
||||
conversationId: string,
|
||||
messageText: string,
|
||||
bodyRanges: DraftBodyRangesType
|
||||
) {
|
||||
const conversation = window.ConversationController.get(conversationId);
|
||||
if (!conversation) {
|
||||
throw new Error('saveDraft: Unable to find conversation');
|
||||
}
|
||||
|
||||
const trimmed =
|
||||
messageText && messageText.length > 0 ? messageText.trim() : '';
|
||||
|
||||
if (conversation.get('draft') && (!messageText || trimmed.length === 0)) {
|
||||
conversation.set({
|
||||
draft: null,
|
||||
draftChanged: true,
|
||||
draftBodyRanges: [],
|
||||
});
|
||||
window.Signal.Data.updateConversation(conversation.attributes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageText !== conversation.get('draft')) {
|
||||
const now = Date.now();
|
||||
let activeAt = conversation.get('active_at');
|
||||
let timestamp = conversation.get('timestamp');
|
||||
|
||||
if (!activeAt) {
|
||||
activeAt = now;
|
||||
timestamp = now;
|
||||
}
|
||||
|
||||
conversation.set({
|
||||
active_at: activeAt,
|
||||
draft: messageText,
|
||||
draftBodyRanges: bodyRanges,
|
||||
draftChanged: true,
|
||||
timestamp,
|
||||
});
|
||||
window.Signal.Data.updateConversation(conversation.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
function setComposerDisabledState(
|
||||
value: boolean
|
||||
|
@ -694,6 +800,7 @@ function setQuotedMessage(
|
|||
export function getEmptyState(): ComposerStateType {
|
||||
return {
|
||||
attachments: [],
|
||||
focusCounter: 0,
|
||||
isDisabled: false,
|
||||
linkPreviewLoading: false,
|
||||
messageCompositionId: UUID.generate().toString(),
|
||||
|
@ -719,6 +826,13 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === SET_FOCUS) {
|
||||
return {
|
||||
...state,
|
||||
focusCounter: state.focusCounter + 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === SET_HIGH_QUALITY_SETTING) {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -70,6 +70,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
|||
|
||||
const {
|
||||
attachments: draftAttachments,
|
||||
focusCounter,
|
||||
isDisabled,
|
||||
linkPreviewLoading,
|
||||
linkPreviewResult,
|
||||
|
@ -83,11 +84,13 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
|||
return {
|
||||
// Base
|
||||
conversationId: id,
|
||||
focusCounter,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
i18n: getIntl(state),
|
||||
isDisabled,
|
||||
messageCompositionId,
|
||||
theme: getTheme(state),
|
||||
|
||||
// AudioCapture
|
||||
errorDialogAudioRecorderType:
|
||||
state.audioRecorder.errorDialogAudioRecorderType,
|
||||
|
|
|
@ -18,7 +18,6 @@ export type PropsType = {
|
|||
compositionAreaProps: Pick<
|
||||
CompositionAreaPropsType,
|
||||
| 'clearQuotedMessage'
|
||||
| 'compositionApi'
|
||||
| 'getQuotedMessage'
|
||||
| 'handleClickQuotedMessage'
|
||||
| 'id'
|
||||
|
|
|
@ -121,6 +121,7 @@ export function SmartForwardMessageModal(): JSX.Element | null {
|
|||
messageBody={cleanedBody}
|
||||
onClose={closeModal}
|
||||
onEditorStateChange={(
|
||||
_conversationId: string | undefined,
|
||||
messageText: string,
|
||||
_: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
|
|
|
@ -107,6 +107,7 @@ describe('both/state/ducks/composer', () => {
|
|||
const nextState = reducer(
|
||||
{
|
||||
attachments: [],
|
||||
focusCounter: 0,
|
||||
isDisabled: false,
|
||||
linkPreviewLoading: true,
|
||||
messageCompositionId: emptyState.messageCompositionId,
|
||||
|
|
|
@ -6,13 +6,12 @@
|
|||
import type * as Backbone from 'backbone';
|
||||
import type { ComponentProps } from 'react';
|
||||
import * as React from 'react';
|
||||
import { debounce, flatten } from 'lodash';
|
||||
import { flatten } from 'lodash';
|
||||
import { render } from 'mustache';
|
||||
|
||||
import type { AttachmentType } from '../types/Attachment';
|
||||
import { isGIF } from '../types/Attachment';
|
||||
import * as Stickers from '../types/Stickers';
|
||||
import type { DraftBodyRangesType } from '../types/Util';
|
||||
import type { MIMEType } from '../types/MIME';
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
|
@ -43,7 +42,6 @@ import { ConversationDetailsMembershipList } from '../components/conversation/co
|
|||
import * as log from '../logging/log';
|
||||
import type { EmbeddedContactType } from '../types/EmbeddedContact';
|
||||
import { createConversationView } from '../state/roots/createConversationView';
|
||||
import type { CompositionAPIType } from '../components/CompositionArea';
|
||||
import { ToastConversationArchived } from '../components/ToastConversationArchived';
|
||||
import { ToastConversationMarkedUnread } from '../components/ToastConversationMarkedUnread';
|
||||
import { ToastConversationUnarchived } from '../components/ToastConversationUnarchived';
|
||||
|
@ -67,16 +65,15 @@ import { ContactDetail } from '../components/conversation/ContactDetail';
|
|||
import { MediaGallery } from '../components/conversation/media-gallery/MediaGallery';
|
||||
import type { ItemClickEvent } from '../components/conversation/media-gallery/types/ItemClickEvent';
|
||||
import {
|
||||
maybeGrabLinkPreview,
|
||||
removeLinkPreview,
|
||||
suspendLinkPreviews,
|
||||
} from '../services/LinkPreview';
|
||||
import { LinkPreviewSourceType } from '../types/LinkPreview';
|
||||
import { closeLightbox, showLightbox } from '../util/showLightbox';
|
||||
import { saveAttachment } from '../util/saveAttachment';
|
||||
import { SECOND } from '../util/durations';
|
||||
import { startConversation } from '../util/startConversation';
|
||||
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
||||
import { hasDraftAttachments } from '../util/hasDraftAttachments';
|
||||
|
||||
type AttachmentOptions = {
|
||||
messageId: string;
|
||||
|
@ -160,16 +157,6 @@ type MediaType = {
|
|||
};
|
||||
|
||||
export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||
private debouncedSaveDraft: (
|
||||
messageText: string,
|
||||
bodyRanges: DraftBodyRangesType
|
||||
) => Promise<void>;
|
||||
|
||||
// Composing messages
|
||||
private compositionApi: {
|
||||
current: CompositionAPIType;
|
||||
} = { current: undefined };
|
||||
|
||||
// Sub-views
|
||||
private contactModalView?: Backbone.View;
|
||||
private conversationView?: Backbone.View;
|
||||
|
@ -184,8 +171,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
constructor(...args: Array<any>) {
|
||||
super(...args);
|
||||
|
||||
this.debouncedSaveDraft = debounce(this.saveDraft.bind(this), 200);
|
||||
|
||||
// Events on Conversation model
|
||||
this.listenTo(this.model, 'destroy', this.stopListening);
|
||||
|
||||
|
@ -197,7 +182,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
);
|
||||
|
||||
// These are triggered by background.ts for keyboard handling
|
||||
this.listenTo(this.model, 'focus-composer', this.focusMessageField);
|
||||
this.listenTo(this.model, 'open-all-media', this.showAllMedia);
|
||||
this.listenTo(this.model, 'escape-pressed', this.resetPanel);
|
||||
this.listenTo(this.model, 'show-message-details', this.showMessageDetail);
|
||||
|
@ -416,13 +400,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
|
||||
const compositionAreaProps = {
|
||||
id: this.model.id,
|
||||
compositionApi: this.compositionApi,
|
||||
onClickAddPack: () => this.showStickerManager(),
|
||||
onEditorStateChange: (
|
||||
msg: string,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
) => this.onEditorStateChange(msg, bodyRanges, caretLocation),
|
||||
onTextTooLong: () => showToast(ToastMessageBodyTooLong),
|
||||
getQuotedMessage: () => this.model.get('quotedMessageId'),
|
||||
clearQuotedMessage: () => this.setQuoteMessage(undefined),
|
||||
|
@ -736,7 +714,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
|
||||
loadAndUpdate();
|
||||
|
||||
this.focusMessageField();
|
||||
window.reduxActions.composer.setComposerFocus(this.model.id);
|
||||
|
||||
const quotedMessageId = this.model.get('quotedMessageId');
|
||||
if (quotedMessageId) {
|
||||
|
@ -1619,78 +1597,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
);
|
||||
}
|
||||
|
||||
focusMessageField(): void {
|
||||
if (this.panels && this.panels.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.compositionApi.current?.focusInput();
|
||||
}
|
||||
|
||||
resetEmojiResults(): void {
|
||||
this.compositionApi.current?.resetEmojiResults();
|
||||
}
|
||||
|
||||
onEditorStateChange(
|
||||
messageText: string,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
): void {
|
||||
if (messageText.length && this.model.throttledBumpTyping) {
|
||||
this.model.throttledBumpTyping();
|
||||
}
|
||||
|
||||
this.debouncedSaveDraft(messageText, bodyRanges);
|
||||
|
||||
// If we have attachments, don't add link preview
|
||||
if (!this.hasFiles({ includePending: true })) {
|
||||
maybeGrabLinkPreview(messageText, LinkPreviewSourceType.Composer, {
|
||||
caretLocation,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async saveDraft(
|
||||
messageText: string,
|
||||
bodyRanges: DraftBodyRangesType
|
||||
): Promise<void> {
|
||||
const { model }: { model: ConversationModel } = this;
|
||||
|
||||
const trimmed =
|
||||
messageText && messageText.length > 0 ? messageText.trim() : '';
|
||||
|
||||
if (model.get('draft') && (!messageText || trimmed.length === 0)) {
|
||||
this.model.set({
|
||||
draft: null,
|
||||
draftChanged: true,
|
||||
draftBodyRanges: [],
|
||||
});
|
||||
await this.saveModel();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageText !== model.get('draft')) {
|
||||
const now = Date.now();
|
||||
let active_at = this.model.get('active_at');
|
||||
let timestamp = this.model.get('timestamp');
|
||||
|
||||
if (!active_at) {
|
||||
active_at = now;
|
||||
timestamp = now;
|
||||
}
|
||||
|
||||
this.model.set({
|
||||
active_at,
|
||||
draft: messageText,
|
||||
draftBodyRanges: bodyRanges,
|
||||
draftChanged: true,
|
||||
timestamp,
|
||||
});
|
||||
await this.saveModel();
|
||||
}
|
||||
}
|
||||
|
||||
async setQuoteMessage(messageId: string | undefined): Promise<void> {
|
||||
const { model } = this;
|
||||
const message = messageId ? await getMessageById(messageId) : undefined;
|
||||
|
@ -1738,8 +1644,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
quote,
|
||||
});
|
||||
|
||||
window.reduxActions.composer.setComposerFocus(this.model.id);
|
||||
window.reduxActions.composer.setComposerDisabledState(false);
|
||||
this.focusMessageField();
|
||||
} else {
|
||||
window.reduxActions.composer.setQuotedMessage(undefined);
|
||||
}
|
||||
|
@ -1763,22 +1669,13 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
]);
|
||||
}
|
||||
|
||||
hasFiles(options: { includePending: boolean }): boolean {
|
||||
const draftAttachments = this.model.get('draftAttachments') || [];
|
||||
if (options.includePending) {
|
||||
return draftAttachments.length > 0;
|
||||
}
|
||||
|
||||
return draftAttachments.some(item => !item.pending);
|
||||
}
|
||||
|
||||
updateAttachmentsView(): void {
|
||||
const draftAttachments = this.model.get('draftAttachments') || [];
|
||||
window.reduxActions.composer.replaceAttachments(
|
||||
this.model.get('id'),
|
||||
draftAttachments
|
||||
);
|
||||
if (this.hasFiles({ includePending: true })) {
|
||||
if (hasDraftAttachments(this.model.attributes, { includePending: true })) {
|
||||
removeLinkPreview();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue