parent
b6fcd59ec4
commit
e2c26e9819
|
@ -27,7 +27,10 @@ import { SessionQuotedMessageComposition } from './SessionQuotedMessageCompositi
|
|||
import { Mention, MentionsInput } from 'react-mentions';
|
||||
import { CaptionEditor } from '../../CaptionEditor';
|
||||
import { getConversationController } from '../../../session/conversations';
|
||||
import { ReduxConversationType } from '../../../state/ducks/conversations';
|
||||
import {
|
||||
ReduxConversationType,
|
||||
updateDraftForConversation,
|
||||
} from '../../../state/ducks/conversations';
|
||||
import { SessionMemberListItem } from '../SessionMemberListItem';
|
||||
import autoBind from 'auto-bind';
|
||||
import { SessionSettingCategory } from '../settings/SessionSettings';
|
||||
|
@ -44,6 +47,7 @@ import {
|
|||
hasLinkPreviewPopupBeenDisplayed,
|
||||
} from '../../../data/data';
|
||||
import {
|
||||
getDraftForCurrentConversation,
|
||||
getMentionsInput,
|
||||
getQuotedMessage,
|
||||
getSelectedConversation,
|
||||
|
@ -77,6 +81,7 @@ export interface StagedAttachmentType extends AttachmentType {
|
|||
|
||||
interface Props {
|
||||
sendMessage: any;
|
||||
draft: string;
|
||||
|
||||
onLoadVoiceNoteView: any;
|
||||
onExitVoiceNoteView: any;
|
||||
|
@ -90,7 +95,6 @@ interface Props {
|
|||
}
|
||||
|
||||
interface State {
|
||||
message: string;
|
||||
showRecordingView: boolean;
|
||||
|
||||
showEmojiPanel: boolean;
|
||||
|
@ -393,7 +397,7 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
|
||||
private renderTextArea() {
|
||||
const { i18n } = window;
|
||||
const { message } = this.state;
|
||||
const { draft } = this.props;
|
||||
|
||||
if (!this.props.selectedConversation) {
|
||||
return null;
|
||||
|
@ -414,7 +418,7 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
|
||||
return (
|
||||
<MentionsInput
|
||||
value={message}
|
||||
value={draft}
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onKeyUp={this.onKeyUp}
|
||||
|
@ -545,7 +549,7 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
return <></>;
|
||||
}
|
||||
// we try to match the first link found in the current message
|
||||
const links = window.Signal.LinkPreviews.findLinks(this.state.message, undefined);
|
||||
const links = window.Signal.LinkPreviews.findLinks(this.props.draft, undefined);
|
||||
if (!links || links.length === 0 || ignoredLink === links[0]) {
|
||||
return <></>;
|
||||
}
|
||||
|
@ -766,18 +770,18 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private async onKeyUp(event: any) {
|
||||
const { message } = this.state;
|
||||
const { draft } = this.props;
|
||||
// Called whenever the user changes the message composition field. But only
|
||||
// fires if there's content in the message field after the change.
|
||||
// Also, check for a message length change before firing it up, to avoid
|
||||
// catching ESC, tab, or whatever which is not typing
|
||||
if (message.length && message.length !== this.lastBumpTypingMessageLength) {
|
||||
if (draft.length && draft.length !== this.lastBumpTypingMessageLength) {
|
||||
const conversationModel = getConversationController().get(this.props.selectedConversationKey);
|
||||
if (!conversationModel) {
|
||||
return;
|
||||
}
|
||||
conversationModel.throttledBumpTyping();
|
||||
this.lastBumpTypingMessageLength = message.length;
|
||||
this.lastBumpTypingMessageLength = draft.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -809,7 +813,7 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
return replacedMentions;
|
||||
};
|
||||
|
||||
const messagePlaintext = cleanMentions(this.parseEmojis(this.state.message));
|
||||
const messagePlaintext = cleanMentions(this.parseEmojis(this.props.draft));
|
||||
|
||||
const { selectedConversation } = this.props;
|
||||
|
||||
|
@ -876,11 +880,16 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
|
||||
// Empty composition box and stagedAttachments
|
||||
this.setState({
|
||||
message: '',
|
||||
showEmojiPanel: false,
|
||||
stagedLinkPreview: undefined,
|
||||
ignoredLink: undefined,
|
||||
});
|
||||
window.inboxStore?.dispatch(
|
||||
updateDraftForConversation({
|
||||
conversationKey: this.props.selectedConversationKey,
|
||||
draft: '',
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
// Message sending failed
|
||||
window?.log?.error(e);
|
||||
|
@ -959,9 +968,13 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private onChange(event: any) {
|
||||
const message = event.target.value ?? '';
|
||||
|
||||
this.setState({ message });
|
||||
const draft = event.target.value ?? '';
|
||||
window.inboxStore?.dispatch(
|
||||
updateDraftForConversation({
|
||||
conversationKey: this.props.selectedConversationKey,
|
||||
draft,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private getSelectionBasedOnMentions(index: number) {
|
||||
|
@ -969,7 +982,7 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
// this is kind of a pain as the mentions box has two inputs, one with the real text, and one with the extracted mentions
|
||||
|
||||
// the index shown to the user is actually just the visible part of the mentions (so the part between ᅲ...ᅭ
|
||||
const matches = this.state.message.match(this.mentionsRegex);
|
||||
const matches = this.props.draft.match(this.mentionsRegex);
|
||||
|
||||
let lastMatchStartIndex = 0;
|
||||
let lastMatchEndIndex = 0;
|
||||
|
@ -983,7 +996,7 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
const displayNameEnd = match.lastIndexOf('\uFFD2');
|
||||
const displayName = match.substring(displayNameStart, displayNameEnd);
|
||||
|
||||
const currentMatchStartIndex = this.state.message.indexOf(match) + lastMatchStartIndex;
|
||||
const currentMatchStartIndex = this.props.draft.indexOf(match) + lastMatchStartIndex;
|
||||
lastMatchStartIndex = currentMatchStartIndex;
|
||||
lastMatchEndIndex = currentMatchStartIndex + match.length;
|
||||
|
||||
|
@ -1027,30 +1040,34 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
const { message } = this.state;
|
||||
const { draft } = this.props;
|
||||
|
||||
const currentSelectionStart = Number(messageBox.selectionStart);
|
||||
|
||||
const realSelectionStart = this.getSelectionBasedOnMentions(currentSelectionStart);
|
||||
|
||||
const before = message.slice(0, realSelectionStart);
|
||||
const end = message.slice(realSelectionStart);
|
||||
const before = draft.slice(0, realSelectionStart);
|
||||
const end = draft.slice(realSelectionStart);
|
||||
|
||||
const newMessage = `${before}${colons}${end}`;
|
||||
window.inboxStore?.dispatch(
|
||||
updateDraftForConversation({
|
||||
conversationKey: this.props.selectedConversationKey,
|
||||
draft: newMessage,
|
||||
})
|
||||
);
|
||||
|
||||
this.setState({ message: newMessage }, () => {
|
||||
// update our selection because updating text programmatically
|
||||
// will put the selection at the end of the textarea
|
||||
const selectionStart = currentSelectionStart + Number(colons.length);
|
||||
// update our selection because updating text programmatically
|
||||
// will put the selection at the end of the textarea
|
||||
const selectionStart = currentSelectionStart + Number(colons.length);
|
||||
messageBox.selectionStart = selectionStart;
|
||||
messageBox.selectionEnd = selectionStart;
|
||||
|
||||
// Sometimes, we have to repeat the set of the selection position with a timeout to be effective
|
||||
setTimeout(() => {
|
||||
messageBox.selectionStart = selectionStart;
|
||||
messageBox.selectionEnd = selectionStart;
|
||||
|
||||
// Sometimes, we have to repeat the set of the selection position with a timeout to be effective
|
||||
setTimeout(() => {
|
||||
messageBox.selectionStart = selectionStart;
|
||||
messageBox.selectionEnd = selectionStart;
|
||||
}, 20);
|
||||
});
|
||||
}, 20);
|
||||
}
|
||||
|
||||
private focusCompositionBox() {
|
||||
|
@ -1068,6 +1085,7 @@ const mapStateToProps = (state: StateType) => {
|
|||
quotedMessageProps: getQuotedMessage(state),
|
||||
selectedConversation: getSelectedConversation(state),
|
||||
selectedConversationKey: getSelectedConversationKey(state),
|
||||
draft: getDraftForCurrentConversation(state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -269,6 +269,7 @@ export type ConversationsStateType = {
|
|||
animateQuotedMessageId?: string;
|
||||
nextMessageToPlayId?: string;
|
||||
mentionMembers: MentionsMembersType;
|
||||
draftsForConversations: Array<{ conversationKey: string; draft: string }>;
|
||||
};
|
||||
|
||||
export type MentionsMembersType = Array<{
|
||||
|
@ -355,6 +356,7 @@ export function getEmptyConversationState(): ConversationsStateType {
|
|||
mentionMembers: [],
|
||||
firstUnreadMessageId: undefined,
|
||||
haveDoneFirstScroll: false,
|
||||
draftsForConversations: new Array(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -686,6 +688,7 @@ const conversationsSlice = createSlice({
|
|||
firstUnreadMessageId: action.payload.firstUnreadIdOnOpen,
|
||||
|
||||
haveDoneFirstScroll: false,
|
||||
draftsForConversations: state.draftsForConversations,
|
||||
};
|
||||
},
|
||||
updateHaveDoneFirstScroll(state: ConversationsStateType) {
|
||||
|
@ -728,10 +731,24 @@ const conversationsSlice = createSlice({
|
|||
state: ConversationsStateType,
|
||||
action: PayloadAction<MentionsMembersType>
|
||||
) {
|
||||
window?.log?.warn('updating mentions input members length', action.payload?.length);
|
||||
window?.log?.info('updating mentions input members length', action.payload?.length);
|
||||
state.mentionMembers = action.payload;
|
||||
return state;
|
||||
},
|
||||
updateDraftForConversation(
|
||||
state: ConversationsStateType,
|
||||
action: PayloadAction<{ conversationKey: string; draft: string }>
|
||||
) {
|
||||
window?.log?.info('updating draft for conversation');
|
||||
const { conversationKey, draft } = action.payload;
|
||||
const foundAtIndex = state.draftsForConversations.findIndex(
|
||||
c => c.conversationKey === conversationKey
|
||||
);
|
||||
foundAtIndex === -1
|
||||
? state.draftsForConversations.push({ conversationKey, draft })
|
||||
: (state.draftsForConversations[foundAtIndex] = action.payload);
|
||||
return state;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder: any) => {
|
||||
// Add reducers for additional action types here, and handle loading state as needed
|
||||
|
@ -791,6 +808,7 @@ export const {
|
|||
quotedMessageToAnimate,
|
||||
setNextMessageToPlayId,
|
||||
updateMentionsMembers,
|
||||
updateDraftForConversation,
|
||||
} = actions;
|
||||
|
||||
export async function openConversationWithMessages(args: {
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
} from '../../components/conversation/ConversationHeader';
|
||||
import { LightBoxOptions } from '../../components/session/conversation/SessionConversation';
|
||||
import { ReplyingToMessageProps } from '../../components/session/conversation/SessionCompositionBox';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { getConversationController } from '../../session/conversations';
|
||||
|
||||
export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
|
||||
|
@ -367,6 +366,19 @@ export const getMentionsInput = createSelector(
|
|||
(state: ConversationsStateType): MentionsMembersType => state.mentionMembers
|
||||
);
|
||||
|
||||
export const getDraftForCurrentConversation = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): string => {
|
||||
if (state.selectedConversation) {
|
||||
return (
|
||||
state.draftsForConversations.find(c => c.conversationKey === state.selectedConversation)
|
||||
?.draft || ''
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
);
|
||||
|
||||
/// Those calls are just related to ordering messages in the redux store.
|
||||
|
||||
function updateFirstMessageOfSeries(
|
||||
|
|
Loading…
Reference in New Issue