Hydrate body ranges for story replies
This commit is contained in:
parent
9f85db3fd8
commit
be6e988a95
39 changed files with 221 additions and 172 deletions
|
@ -6,8 +6,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type {
|
import type {
|
||||||
BodyRangeType,
|
DraftBodyRangesType,
|
||||||
BodyRangesType,
|
|
||||||
LocalizerType,
|
LocalizerType,
|
||||||
ThemeType,
|
ThemeType,
|
||||||
} from '../types/Util';
|
} from '../types/Util';
|
||||||
|
@ -116,7 +115,7 @@ export type OwnProps = Readonly<{
|
||||||
onSelectMediaQuality(isHQ: boolean): unknown;
|
onSelectMediaQuality(isHQ: boolean): unknown;
|
||||||
onSendMessage(options: {
|
onSendMessage(options: {
|
||||||
draftAttachments?: ReadonlyArray<AttachmentDraftType>;
|
draftAttachments?: ReadonlyArray<AttachmentDraftType>;
|
||||||
mentions?: BodyRangesType;
|
mentions?: DraftBodyRangesType;
|
||||||
message?: string;
|
message?: string;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
voiceNoteAttachment?: InMemoryAttachmentDraftType;
|
voiceNoteAttachment?: InMemoryAttachmentDraftType;
|
||||||
|
@ -276,7 +275,7 @@ export const CompositionArea = ({
|
||||||
}, [inputApiRef, setLarge]);
|
}, [inputApiRef, setLarge]);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(message: string, mentions: Array<BodyRangeType>, timestamp: number) => {
|
(message: string, mentions: DraftBodyRangesType, timestamp: number) => {
|
||||||
emojiButtonRef.current?.close();
|
emojiButtonRef.current?.close();
|
||||||
onSendMessage({
|
onSendMessage({
|
||||||
draftAttachments,
|
draftAttachments,
|
||||||
|
|
|
@ -14,7 +14,11 @@ import { MentionCompletion } from '../quill/mentions/completion';
|
||||||
import { EmojiBlot, EmojiCompletion } from '../quill/emoji';
|
import { EmojiBlot, EmojiCompletion } from '../quill/emoji';
|
||||||
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||||
import { convertShortName } from './emoji/lib';
|
import { convertShortName } from './emoji/lib';
|
||||||
import type { LocalizerType, BodyRangeType, ThemeType } from '../types/Util';
|
import type {
|
||||||
|
LocalizerType,
|
||||||
|
DraftBodyRangesType,
|
||||||
|
ThemeType,
|
||||||
|
} from '../types/Util';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import { isValidUuid } from '../types/UUID';
|
import { isValidUuid } from '../types/UUID';
|
||||||
|
@ -71,7 +75,7 @@ export type Props = Readonly<{
|
||||||
inputApi?: React.MutableRefObject<InputApi | undefined>;
|
inputApi?: React.MutableRefObject<InputApi | undefined>;
|
||||||
skinTone?: EmojiPickDataType['skinTone'];
|
skinTone?: EmojiPickDataType['skinTone'];
|
||||||
draftText?: string;
|
draftText?: string;
|
||||||
draftBodyRanges?: Array<BodyRangeType>;
|
draftBodyRanges?: DraftBodyRangesType;
|
||||||
moduleClassName?: string;
|
moduleClassName?: string;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -80,14 +84,14 @@ export type Props = Readonly<{
|
||||||
onDirtyChange?(dirty: boolean): unknown;
|
onDirtyChange?(dirty: boolean): unknown;
|
||||||
onEditorStateChange?(
|
onEditorStateChange?(
|
||||||
messageText: string,
|
messageText: string,
|
||||||
bodyRanges: Array<BodyRangeType>,
|
bodyRanges: DraftBodyRangesType,
|
||||||
caretLocation?: number
|
caretLocation?: number
|
||||||
): unknown;
|
): unknown;
|
||||||
onTextTooLong(): unknown;
|
onTextTooLong(): unknown;
|
||||||
onPickEmoji(o: EmojiPickDataType): unknown;
|
onPickEmoji(o: EmojiPickDataType): unknown;
|
||||||
onSubmit(
|
onSubmit(
|
||||||
message: string,
|
message: string,
|
||||||
mentions: Array<BodyRangeType>,
|
mentions: DraftBodyRangesType,
|
||||||
timestamp: number
|
timestamp: number
|
||||||
): unknown;
|
): unknown;
|
||||||
onScroll?: (ev: React.UIEvent<HTMLElement>) => void;
|
onScroll?: (ev: React.UIEvent<HTMLElement>) => void;
|
||||||
|
@ -143,7 +147,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
|
|
||||||
const generateDelta = (
|
const generateDelta = (
|
||||||
text: string,
|
text: string,
|
||||||
bodyRanges: Array<BodyRangeType>
|
bodyRanges: DraftBodyRangesType
|
||||||
): Delta => {
|
): Delta => {
|
||||||
const initialOps = [{ insert: text }];
|
const initialOps = [{ insert: text }];
|
||||||
const opsWithMentions = insertMentionOps(initialOps, bodyRanges);
|
const opsWithMentions = insertMentionOps(initialOps, bodyRanges);
|
||||||
|
@ -152,7 +156,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
return new Delta(opsWithEmojis);
|
return new Delta(opsWithEmojis);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTextAndMentions = (): [string, Array<BodyRangeType>] => {
|
const getTextAndMentions = (): [string, DraftBodyRangesType] => {
|
||||||
const quill = quillRef.current;
|
const quill = quillRef.current;
|
||||||
|
|
||||||
if (quill === undefined) {
|
if (quill === undefined) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { shouldNeverBeCalled } from '../util/shouldNeverBeCalled';
|
||||||
import type { InputApi } from './CompositionInput';
|
import type { InputApi } from './CompositionInput';
|
||||||
import { CompositionInput } from './CompositionInput';
|
import { CompositionInput } from './CompositionInput';
|
||||||
import { EmojiButton } from './emoji/EmojiButton';
|
import { EmojiButton } from './emoji/EmojiButton';
|
||||||
import type { BodyRangeType, ThemeType } from '../types/Util';
|
import type { DraftBodyRangesType, ThemeType } from '../types/Util';
|
||||||
import type { Props as EmojiButtonProps } from './emoji/EmojiButton';
|
import type { Props as EmojiButtonProps } from './emoji/EmojiButton';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import * as grapheme from '../util/grapheme';
|
import * as grapheme from '../util/grapheme';
|
||||||
|
@ -24,13 +24,13 @@ export type CompositionTextAreaProps = {
|
||||||
onPickEmoji: (e: EmojiPickDataType) => void;
|
onPickEmoji: (e: EmojiPickDataType) => void;
|
||||||
onChange: (
|
onChange: (
|
||||||
messageText: string,
|
messageText: string,
|
||||||
bodyRanges: Array<BodyRangeType>,
|
bodyRanges: DraftBodyRangesType,
|
||||||
caretLocation?: number | undefined
|
caretLocation?: number | undefined
|
||||||
) => void;
|
) => void;
|
||||||
onSetSkinTone: (tone: number) => void;
|
onSetSkinTone: (tone: number) => void;
|
||||||
onSubmit: (
|
onSubmit: (
|
||||||
message: string,
|
message: string,
|
||||||
mentions: Array<BodyRangeType>,
|
mentions: DraftBodyRangesType,
|
||||||
timestamp: number
|
timestamp: number
|
||||||
) => void;
|
) => void;
|
||||||
onTextTooLong: () => void;
|
onTextTooLong: () => void;
|
||||||
|
@ -88,7 +88,7 @@ export const CompositionTextArea = ({
|
||||||
const handleChange = React.useCallback(
|
const handleChange = React.useCallback(
|
||||||
(
|
(
|
||||||
newValue: string,
|
newValue: string,
|
||||||
bodyRanges: Array<BodyRangeType>,
|
bodyRanges: DraftBodyRangesType,
|
||||||
caretLocation?: number | undefined
|
caretLocation?: number | undefined
|
||||||
) => {
|
) => {
|
||||||
const inputEl = inputApiRef.current;
|
const inputEl = inputApiRef.current;
|
||||||
|
|
|
@ -24,7 +24,11 @@ import { ConversationList, RowType } from './ConversationList';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||||
import type { BodyRangeType, LocalizerType, ThemeType } from '../types/Util';
|
import type {
|
||||||
|
DraftBodyRangesType,
|
||||||
|
LocalizerType,
|
||||||
|
ThemeType,
|
||||||
|
} from '../types/Util';
|
||||||
import type { SmartCompositionTextAreaProps } from '../state/smart/CompositionTextArea';
|
import type { SmartCompositionTextAreaProps } from '../state/smart/CompositionTextArea';
|
||||||
import { ModalHost } from './ModalHost';
|
import { ModalHost } from './ModalHost';
|
||||||
import { SearchInput } from './SearchInput';
|
import { SearchInput } from './SearchInput';
|
||||||
|
@ -54,7 +58,7 @@ export type DataPropsType = {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onEditorStateChange: (
|
onEditorStateChange: (
|
||||||
messageText: string,
|
messageText: string,
|
||||||
bodyRanges: Array<BodyRangeType>,
|
bodyRanges: DraftBodyRangesType,
|
||||||
caretLocation?: number
|
caretLocation?: number
|
||||||
) => unknown;
|
) => unknown;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
|
|
|
@ -27,13 +27,11 @@ export type Props = {
|
||||||
renderText?: RenderTextCallbackType;
|
renderText?: RenderTextCallbackType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Intl extends React.Component<Props> {
|
const defaultRenderText: RenderTextCallbackType = ({ text, key }) => (
|
||||||
public static defaultProps: Partial<Props> = {
|
<React.Fragment key={key}>{text}</React.Fragment>
|
||||||
renderText: ({ text, key }) => (
|
);
|
||||||
<React.Fragment key={key}>{text}</React.Fragment>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export class Intl extends React.Component<Props> {
|
||||||
public getComponent(
|
public getComponent(
|
||||||
index: number,
|
index: number,
|
||||||
placeholderName: string,
|
placeholderName: string,
|
||||||
|
@ -74,7 +72,7 @@ export class Intl extends React.Component<Props> {
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
public override render() {
|
public override render() {
|
||||||
const { components, id, i18n, renderText } = this.props;
|
const { components, id, i18n, renderText = defaultRenderText } = this.props;
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
log.error('Error: Intl id prop not provided');
|
log.error('Error: Intl id prop not provided');
|
||||||
|
@ -96,12 +94,6 @@ export class Intl extends React.Component<Props> {
|
||||||
> = [];
|
> = [];
|
||||||
const FIND_REPLACEMENTS = /\$([^$]+)\$/g;
|
const FIND_REPLACEMENTS = /\$([^$]+)\$/g;
|
||||||
|
|
||||||
// We have to do this, because renderText is not required in our Props object,
|
|
||||||
// but it is always provided via defaultProps.
|
|
||||||
if (!renderText) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(components) && components.length > 1) {
|
if (Array.isArray(components) && components.length > 1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Array syntax is not supported with more than one placeholder'
|
'Array syntax is not supported with more than one placeholder'
|
||||||
|
|
|
@ -10,7 +10,7 @@ import React, {
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { BodyRangeType, LocalizerType } from '../types/Util';
|
import type { DraftBodyRangesType, LocalizerType } from '../types/Util';
|
||||||
import type { ContextMenuOptionType } from './ContextMenu';
|
import type { ContextMenuOptionType } from './ContextMenu';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||||
|
@ -80,7 +80,7 @@ export type PropsType = {
|
||||||
onReactToStory: (emoji: string, story: StoryViewType) => unknown;
|
onReactToStory: (emoji: string, story: StoryViewType) => unknown;
|
||||||
onReplyToStory: (
|
onReplyToStory: (
|
||||||
message: string,
|
message: string,
|
||||||
mentions: Array<BodyRangeType>,
|
mentions: DraftBodyRangesType,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
story: StoryViewType
|
story: StoryViewType
|
||||||
) => unknown;
|
) => unknown;
|
||||||
|
|
|
@ -10,8 +10,10 @@ import React, {
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { usePopper } from 'react-popper';
|
import { usePopper } from 'react-popper';
|
||||||
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import type { BodyRangeType, LocalizerType } from '../types/Util';
|
import type { DraftBodyRangesType, LocalizerType } from '../types/Util';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||||
import type { InputApi } from './CompositionInput';
|
import type { InputApi } from './CompositionInput';
|
||||||
|
@ -56,7 +58,8 @@ const MESSAGE_DEFAULT_PROPS = {
|
||||||
markAttachmentAsCorrupted: shouldNeverBeCalled,
|
markAttachmentAsCorrupted: shouldNeverBeCalled,
|
||||||
markViewed: shouldNeverBeCalled,
|
markViewed: shouldNeverBeCalled,
|
||||||
messageExpanded: shouldNeverBeCalled,
|
messageExpanded: shouldNeverBeCalled,
|
||||||
openConversation: shouldNeverBeCalled,
|
// Called when clicking mention, but shouldn't do anything.
|
||||||
|
openConversation: noop,
|
||||||
openGiftBadge: shouldNeverBeCalled,
|
openGiftBadge: shouldNeverBeCalled,
|
||||||
openLink: shouldNeverBeCalled,
|
openLink: shouldNeverBeCalled,
|
||||||
previews: [],
|
previews: [],
|
||||||
|
@ -90,7 +93,7 @@ export type PropsType = {
|
||||||
onReact: (emoji: string) => unknown;
|
onReact: (emoji: string) => unknown;
|
||||||
onReply: (
|
onReply: (
|
||||||
message: string,
|
message: string,
|
||||||
mentions: Array<BodyRangeType>,
|
mentions: DraftBodyRangesType,
|
||||||
timestamp: number
|
timestamp: number
|
||||||
) => unknown;
|
) => unknown;
|
||||||
onSetSkinTone: (tone: number) => unknown;
|
onSetSkinTone: (tone: number) => unknown;
|
||||||
|
@ -315,6 +318,7 @@ export const StoryViewsNRepliesModal = ({
|
||||||
key={reply.id}
|
key={reply.id}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
reply={reply}
|
reply={reply}
|
||||||
|
reactionEmoji={reply.reactionEmoji}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -504,12 +508,14 @@ export const StoryViewsNRepliesModal = ({
|
||||||
type ReactionProps = {
|
type ReactionProps = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
reply: ReplyType;
|
reply: ReplyType;
|
||||||
|
reactionEmoji: string;
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Reaction = ({
|
const Reaction = ({
|
||||||
i18n,
|
i18n,
|
||||||
reply,
|
reply,
|
||||||
|
reactionEmoji,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
}: ReactionProps): JSX.Element => {
|
}: ReactionProps): JSX.Element => {
|
||||||
// TODO: DESKTOP-4503 - reactions delete/doe
|
// TODO: DESKTOP-4503 - reactions delete/doe
|
||||||
|
@ -546,7 +552,7 @@ const Reaction = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Emojify text={reply.reactionEmoji} />
|
<Emojify text={reactionEmoji} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,27 +11,18 @@ export type Props = {
|
||||||
renderNonNewLine?: RenderTextCallbackType;
|
renderNonNewLine?: RenderTextCallbackType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AddNewLines extends React.Component<Props> {
|
const defaultRenderNonNewLine: RenderTextCallbackType = ({ text }) => text;
|
||||||
public static defaultProps: Partial<Props> = {
|
|
||||||
renderNonNewLine: ({ text }) => text,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export class AddNewLines extends React.Component<Props> {
|
||||||
public override render():
|
public override render():
|
||||||
| JSX.Element
|
| JSX.Element
|
||||||
| string
|
| string
|
||||||
| null
|
| null
|
||||||
| Array<JSX.Element | string | null> {
|
| Array<JSX.Element | string | null> {
|
||||||
const { text, renderNonNewLine } = this.props;
|
const { text, renderNonNewLine = defaultRenderNonNewLine } = this.props;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const results: Array<JSX.Element | string> = [];
|
||||||
const results: Array<any> = [];
|
|
||||||
const FIND_NEWLINES = /\n/g;
|
const FIND_NEWLINES = /\n/g;
|
||||||
|
|
||||||
// We have to do this, because renderNonNewLine is not required in our Props object,
|
|
||||||
// but it is always provided via defaultProps.
|
|
||||||
if (!renderNonNewLine) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let match = FIND_NEWLINES.exec(text);
|
let match = FIND_NEWLINES.exec(text);
|
||||||
let last = 0;
|
let last = 0;
|
||||||
let count = 1;
|
let count = 1;
|
||||||
|
|
|
@ -43,18 +43,21 @@ export const MultipleMentions = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'abc',
|
mentionUuid: 'abc',
|
||||||
replacementText: 'Professor Farnsworth',
|
replacementText: 'Professor Farnsworth',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 2,
|
start: 2,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'def',
|
mentionUuid: 'def',
|
||||||
replacementText: 'Philip J Fry',
|
replacementText: 'Philip J Fry',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0,
|
start: 0,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'xyz',
|
mentionUuid: 'xyz',
|
||||||
replacementText: 'Yancy Fry',
|
replacementText: 'Yancy Fry',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
|
@ -77,18 +80,21 @@ export const ComplexMentions = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'ioe',
|
mentionUuid: 'ioe',
|
||||||
replacementText: 'Cereal Killer',
|
replacementText: 'Cereal Killer',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 78,
|
start: 78,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'fdr',
|
mentionUuid: 'fdr',
|
||||||
replacementText: 'Acid Burn',
|
replacementText: 'Acid Burn',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 4,
|
start: 4,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'ope',
|
mentionUuid: 'ope',
|
||||||
replacementText: 'Zero Cool',
|
replacementText: 'Zero Cool',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
import { Emojify } from './Emojify';
|
import { Emojify } from './Emojify';
|
||||||
import type { BodyRangesType } from '../../types/Util';
|
import type {
|
||||||
|
BodyRangesType,
|
||||||
|
HydratedBodyRangeType,
|
||||||
|
HydratedBodyRangesType,
|
||||||
|
} from '../../types/Util';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: HydratedBodyRangesType;
|
||||||
direction?: 'incoming' | 'outgoing';
|
direction?: 'incoming' | 'outgoing';
|
||||||
openConversation?: (conversationId: string, messageId?: string) => void;
|
openConversation?: (conversationId: string, messageId?: string) => void;
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -28,7 +32,7 @@ export const AtMentionify = ({
|
||||||
let match = MENTIONS_REGEX.exec(text);
|
let match = MENTIONS_REGEX.exec(text);
|
||||||
let last = 0;
|
let last = 0;
|
||||||
|
|
||||||
const rangeStarts = new Map();
|
const rangeStarts = new Map<number, HydratedBodyRangeType>();
|
||||||
bodyRanges.forEach(range => {
|
bodyRanges.forEach(range => {
|
||||||
rangeStarts.set(range.start, range);
|
rangeStarts.set(range.start, range);
|
||||||
});
|
});
|
||||||
|
@ -49,7 +53,7 @@ export const AtMentionify = ({
|
||||||
className={`MessageBody__at-mention MessageBody__at-mention--${direction}`}
|
className={`MessageBody__at-mention MessageBody__at-mention--${direction}`}
|
||||||
key={range.start}
|
key={range.start}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (openConversation && range.conversationID) {
|
if (openConversation) {
|
||||||
openConversation(range.conversationID);
|
openConversation(range.conversationID);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -57,8 +61,7 @@ export const AtMentionify = ({
|
||||||
if (
|
if (
|
||||||
e.target === e.currentTarget &&
|
e.target === e.currentTarget &&
|
||||||
e.keyCode === 13 &&
|
e.keyCode === 13 &&
|
||||||
openConversation &&
|
openConversation
|
||||||
range.conversationID
|
|
||||||
) {
|
) {
|
||||||
openConversation(range.conversationID);
|
openConversation(range.conversationID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const ChangeNumberNotification: React.FC<Props> = props => {
|
||||||
<Intl
|
<Intl
|
||||||
id="ChangeNumber--notification"
|
id="ChangeNumber--notification"
|
||||||
components={{
|
components={{
|
||||||
sender: <Emojify text={sender.title || sender.firstName} />,
|
sender: <Emojify text={sender.title || sender.firstName || ''} />,
|
||||||
}}
|
}}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -308,6 +308,22 @@ export const ContactSpoofingReviewDialog: FunctionComponent<
|
||||||
conversationInfo.conversation.profileName ||
|
conversationInfo.conversation.profileName ||
|
||||||
conversationInfo.conversation.title;
|
conversationInfo.conversation.title;
|
||||||
|
|
||||||
|
let callout: JSX.Element | undefined;
|
||||||
|
if (oldName && oldName !== newName) {
|
||||||
|
callout = (
|
||||||
|
<div className="module-ContactSpoofingReviewDialogPerson__info__property module-ContactSpoofingReviewDialogPerson__info__property--callout">
|
||||||
|
<Intl
|
||||||
|
i18n={i18n}
|
||||||
|
id="ContactSpoofingReviewDialog__group__name-change-info"
|
||||||
|
components={{
|
||||||
|
oldName: <Emojify text={oldName} />,
|
||||||
|
newName: <Emojify text={newName} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{index !== 0 && <hr />}
|
{index !== 0 && <hr />}
|
||||||
|
@ -318,18 +334,7 @@ export const ContactSpoofingReviewDialog: FunctionComponent<
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
>
|
>
|
||||||
{Boolean(oldName) && oldName !== newName && (
|
{callout}
|
||||||
<div className="module-ContactSpoofingReviewDialogPerson__info__property module-ContactSpoofingReviewDialogPerson__info__property--callout">
|
|
||||||
<Intl
|
|
||||||
i18n={i18n}
|
|
||||||
id="ContactSpoofingReviewDialog__group__name-change-info"
|
|
||||||
components={{
|
|
||||||
oldName: <Emojify text={oldName} />,
|
|
||||||
newName: <Emojify text={newName} />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{button && (
|
{button && (
|
||||||
<div className="module-ContactSpoofingReviewDialog__buttons">
|
<div className="module-ContactSpoofingReviewDialog__buttons">
|
||||||
{button}
|
{button}
|
||||||
|
|
|
@ -49,19 +49,15 @@ export type Props = {
|
||||||
renderNonEmoji?: RenderTextCallbackType;
|
renderNonEmoji?: RenderTextCallbackType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultRenderNonEmoji: RenderTextCallbackType = ({ text }) => text;
|
||||||
|
|
||||||
export class Emojify extends React.Component<Props> {
|
export class Emojify extends React.Component<Props> {
|
||||||
public static defaultProps: Partial<Props> = {
|
|
||||||
renderNonEmoji: ({ text }) => text,
|
|
||||||
};
|
|
||||||
|
|
||||||
public override render(): null | Array<JSX.Element | string | null> {
|
public override render(): null | Array<JSX.Element | string | null> {
|
||||||
const { text, sizeClass, renderNonEmoji } = this.props;
|
const {
|
||||||
|
text,
|
||||||
// We have to do this, because renderNonEmoji is not required in our Props object,
|
sizeClass,
|
||||||
// but it is always provided via defaultProps.
|
renderNonEmoji = defaultRenderNonEmoji,
|
||||||
if (!renderNonEmoji) {
|
} = this.props;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return splitByEmoji(text).map(({ type, value: match }, index) => {
|
return splitByEmoji(text).map(({ type, value: match }, index) => {
|
||||||
if (type === 'emoji') {
|
if (type === 'emoji') {
|
||||||
|
|
|
@ -321,28 +321,20 @@ export type Props = {
|
||||||
|
|
||||||
const SUPPORTED_PROTOCOLS = /^(http|https):/i;
|
const SUPPORTED_PROTOCOLS = /^(http|https):/i;
|
||||||
|
|
||||||
export class Linkify extends React.Component<Props> {
|
const defaultRenderNonLink: RenderTextCallbackType = ({ text }) => text;
|
||||||
public static defaultProps: Partial<Props> = {
|
|
||||||
renderNonLink: ({ text }) => text,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export class Linkify extends React.Component<Props> {
|
||||||
public override render():
|
public override render():
|
||||||
| JSX.Element
|
| JSX.Element
|
||||||
| string
|
| string
|
||||||
| null
|
| null
|
||||||
| Array<JSX.Element | string | null> {
|
| Array<JSX.Element | string | null> {
|
||||||
const { text, renderNonLink } = this.props;
|
const { text, renderNonLink = defaultRenderNonLink } = this.props;
|
||||||
|
|
||||||
if (!shouldLinkifyMessage(text)) {
|
if (!shouldLinkifyMessage(text)) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have to do this, because renderNonLink is not required in our Props object,
|
|
||||||
// but it is always provided via defaultProps.
|
|
||||||
if (!renderNonLink) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunkData: Array<{
|
const chunkData: Array<{
|
||||||
chunk: string;
|
chunk: string;
|
||||||
matchData: ReadonlyArray<LinkifyIt.Match>;
|
matchData: ReadonlyArray<LinkifyIt.Match>;
|
||||||
|
|
|
@ -63,7 +63,7 @@ import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
|
||||||
import { isFileDangerous } from '../../util/isFileDangerous';
|
import { isFileDangerous } from '../../util/isFileDangerous';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import type {
|
import type {
|
||||||
BodyRangesType,
|
HydratedBodyRangesType,
|
||||||
LocalizerType,
|
LocalizerType,
|
||||||
ThemeType,
|
ThemeType,
|
||||||
} from '../../types/Util';
|
} from '../../types/Util';
|
||||||
|
@ -228,7 +228,7 @@ export type PropsData = {
|
||||||
authorProfileName?: string;
|
authorProfileName?: string;
|
||||||
authorTitle: string;
|
authorTitle: string;
|
||||||
authorName?: string;
|
authorName?: string;
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: HydratedBodyRangesType;
|
||||||
referencedMessageNotFound: boolean;
|
referencedMessageNotFound: boolean;
|
||||||
isViewOnce: boolean;
|
isViewOnce: boolean;
|
||||||
isGiftBadge: boolean;
|
isGiftBadge: boolean;
|
||||||
|
@ -261,7 +261,7 @@ export type PropsData = {
|
||||||
canDeleteForEveryone: boolean;
|
canDeleteForEveryone: boolean;
|
||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
isMessageRequestAccepted: boolean;
|
isMessageRequestAccepted: boolean;
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: HydratedBodyRangesType;
|
||||||
|
|
||||||
menu: JSX.Element | undefined;
|
menu: JSX.Element | undefined;
|
||||||
onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||||
|
|
|
@ -114,6 +114,7 @@ export const Mention = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'tuv',
|
mentionUuid: 'tuv',
|
||||||
replacementText: 'Bender B Rodriguez 🤖',
|
replacementText: 'Bender B Rodriguez 🤖',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
text: 'Like \uFFFC once said: My story is a lot like yours, only more interesting because it involves robots',
|
text: 'Like \uFFFC once said: My story is a lot like yours, only more interesting because it involves robots',
|
||||||
|
@ -135,18 +136,21 @@ export const MultipleMentions = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'def',
|
mentionUuid: 'def',
|
||||||
replacementText: 'Philip J Fry',
|
replacementText: 'Philip J Fry',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 4,
|
start: 4,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'abc',
|
mentionUuid: 'abc',
|
||||||
replacementText: 'Professor Farnsworth',
|
replacementText: 'Professor Farnsworth',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0,
|
start: 0,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'xyz',
|
mentionUuid: 'xyz',
|
||||||
replacementText: 'Yancy Fry',
|
replacementText: 'Yancy Fry',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
text: '\uFFFC \uFFFC \uFFFC',
|
text: '\uFFFC \uFFFC \uFFFC',
|
||||||
|
@ -168,18 +172,21 @@ export const ComplexMessageBody = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'wer',
|
mentionUuid: 'wer',
|
||||||
replacementText: 'Acid Burn',
|
replacementText: 'Acid Burn',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 80,
|
start: 80,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'xox',
|
mentionUuid: 'xox',
|
||||||
replacementText: 'Cereal Killer',
|
replacementText: 'Cereal Killer',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 4,
|
start: 4,
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'ldo',
|
mentionUuid: 'ldo',
|
||||||
replacementText: 'Zero Cool',
|
replacementText: 'Zero Cool',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
direction: 'outgoing',
|
direction: 'outgoing',
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { AddNewLines } from './AddNewLines';
|
||||||
import { Linkify } from './Linkify';
|
import { Linkify } from './Linkify';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BodyRangesType,
|
HydratedBodyRangesType,
|
||||||
LocalizerType,
|
LocalizerType,
|
||||||
RenderTextCallbackType,
|
RenderTextCallbackType,
|
||||||
} from '../../types/Util';
|
} from '../../types/Util';
|
||||||
|
@ -34,7 +34,7 @@ export type Props = {
|
||||||
/** If set, links will be left alone instead of turned into clickable `<a>` tags. */
|
/** If set, links will be left alone instead of turned into clickable `<a>` tags. */
|
||||||
disableLinks?: boolean;
|
disableLinks?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: HydratedBodyRangesType;
|
||||||
onIncreaseTextLength?: () => unknown;
|
onIncreaseTextLength?: () => unknown;
|
||||||
openConversation?: OpenConversationActionType;
|
openConversation?: OpenConversationActionType;
|
||||||
kickOffBodyDownload?: () => void;
|
kickOffBodyDownload?: () => void;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import * as GoogleChrome from '../../util/GoogleChrome';
|
||||||
|
|
||||||
import { MessageBody } from './MessageBody';
|
import { MessageBody } from './MessageBody';
|
||||||
import type { AttachmentType, ThumbnailType } from '../../types/Attachment';
|
import type { AttachmentType, ThumbnailType } from '../../types/Attachment';
|
||||||
import type { BodyRangesType, LocalizerType } from '../../types/Util';
|
import type { HydratedBodyRangesType, LocalizerType } from '../../types/Util';
|
||||||
import type {
|
import type {
|
||||||
ConversationColorType,
|
ConversationColorType,
|
||||||
CustomColorType,
|
CustomColorType,
|
||||||
|
@ -27,7 +27,7 @@ export type Props = {
|
||||||
authorTitle: string;
|
authorTitle: string;
|
||||||
conversationColor: ConversationColorType;
|
conversationColor: ConversationColorType;
|
||||||
customColor?: CustomColorType;
|
customColor?: CustomColorType;
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: HydratedBodyRangesType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isFromMe: boolean;
|
isFromMe: boolean;
|
||||||
isIncoming?: boolean;
|
isIncoming?: boolean;
|
||||||
|
|
|
@ -1606,6 +1606,7 @@ Mentions.args = {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'zap',
|
mentionUuid: 'zap',
|
||||||
replacementText: 'Zapp Brannigan',
|
replacementText: 'Zapp Brannigan',
|
||||||
|
conversationID: 'x',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
text: '\uFFFC This Is It. The Moment We Should Have Trained For.',
|
text: '\uFFFC This Is It. The Moment We Should Have Trained For.',
|
||||||
|
|
|
@ -19,12 +19,8 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DocumentListItem extends React.Component<Props> {
|
export class DocumentListItem extends React.Component<Props> {
|
||||||
public static defaultProps: Partial<Props> = {
|
|
||||||
shouldShowSeparator: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
public override render(): JSX.Element {
|
public override render(): JSX.Element {
|
||||||
const { shouldShowSeparator } = this.props;
|
const { shouldShowSeparator = true } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -52,6 +52,7 @@ export const TwoReplacementsWithAnMention = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '0ca40892-7b1a-11eb-9439-0242ac130002',
|
mentionUuid: '0ca40892-7b1a-11eb-9439-0242ac130002',
|
||||||
replacementText: 'Jin Sakai',
|
replacementText: 'Jin Sakai',
|
||||||
|
conversationID: 'x',
|
||||||
start: 33,
|
start: 33,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { AddNewLines } from '../conversation/AddNewLines';
|
||||||
import type { SizeClassType } from '../emoji/lib';
|
import type { SizeClassType } from '../emoji/lib';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BodyRangesType,
|
HydratedBodyRangesType,
|
||||||
LocalizerType,
|
LocalizerType,
|
||||||
RenderTextCallbackType,
|
RenderTextCallbackType,
|
||||||
} from '../../types/Util';
|
} from '../../types/Util';
|
||||||
|
@ -21,7 +21,7 @@ import type {
|
||||||
const CLASS_NAME = `${MESSAGE_TEXT_CLASS_NAME}__message-search-result-contents`;
|
const CLASS_NAME = `${MESSAGE_TEXT_CLASS_NAME}__message-search-result-contents`;
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
bodyRanges: BodyRangesType;
|
bodyRanges: HydratedBodyRangesType;
|
||||||
text: string;
|
text: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
};
|
};
|
||||||
|
|
|
@ -206,12 +206,14 @@ export const Mention = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||||
replacementText: 'Shoe',
|
replacementText: 'Shoe',
|
||||||
|
conversationID: 'x',
|
||||||
start: 113,
|
start: 113,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||||
replacementText: 'Shoe',
|
replacementText: 'Shoe',
|
||||||
|
conversationID: 'x',
|
||||||
start: 237,
|
start: 237,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -236,6 +238,7 @@ export const MentionRegexp = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||||
replacementText: 'RegExp',
|
replacementText: 'RegExp',
|
||||||
|
conversationID: 'x',
|
||||||
start: 0,
|
start: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -260,6 +263,7 @@ export const MentionNoMatches = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||||
replacementText: 'Neo',
|
replacementText: 'Neo',
|
||||||
|
conversationID: 'x',
|
||||||
start: 0,
|
start: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -283,12 +287,14 @@ export const _MentionNoMatches = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||||
replacementText: 'Shoe',
|
replacementText: 'Shoe',
|
||||||
|
conversationID: 'x',
|
||||||
start: 113,
|
start: 113,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||||
replacementText: 'Shoe',
|
replacementText: 'Shoe',
|
||||||
|
conversationID: 'x',
|
||||||
start: 237,
|
start: 237,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -313,12 +319,14 @@ export const DoubleMention = (): JSX.Element => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '9eb2eb65-992a-4909-a2a5-18c56bd7648f',
|
mentionUuid: '9eb2eb65-992a-4909-a2a5-18c56bd7648f',
|
||||||
replacementText: 'Alice',
|
replacementText: 'Alice',
|
||||||
|
conversationID: 'x',
|
||||||
start: 4,
|
start: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: '755ec61b-1590-48da-b003-3e57b2b54448',
|
mentionUuid: '755ec61b-1590-48da-b003-3e57b2b54448',
|
||||||
replacementText: 'Bob',
|
replacementText: 'Bob',
|
||||||
|
conversationID: 'x',
|
||||||
start: 6,
|
start: 6,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { ContactName } from '../conversation/ContactName';
|
||||||
|
|
||||||
import { assertDev } from '../../util/assert';
|
import { assertDev } from '../../util/assert';
|
||||||
import type {
|
import type {
|
||||||
BodyRangesType,
|
HydratedBodyRangesType,
|
||||||
LocalizerType,
|
LocalizerType,
|
||||||
ThemeType,
|
ThemeType,
|
||||||
} from '../../types/Util';
|
} from '../../types/Util';
|
||||||
|
@ -31,7 +31,7 @@ export type PropsDataType = {
|
||||||
|
|
||||||
snippet: string;
|
snippet: string;
|
||||||
body: string;
|
body: string;
|
||||||
bodyRanges: BodyRangesType;
|
bodyRanges: HydratedBodyRangesType;
|
||||||
|
|
||||||
from: Pick<
|
from: Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
@ -82,8 +82,8 @@ const renderPerson = (
|
||||||
function getFilteredBodyRanges(
|
function getFilteredBodyRanges(
|
||||||
snippet: string,
|
snippet: string,
|
||||||
body: string,
|
body: string,
|
||||||
bodyRanges: BodyRangesType
|
bodyRanges: HydratedBodyRangesType
|
||||||
): BodyRangesType {
|
): HydratedBodyRangesType {
|
||||||
if (!bodyRanges.length) {
|
if (!bodyRanges.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
4
ts/model-types.d.ts
vendored
4
ts/model-types.d.ts
vendored
|
@ -6,7 +6,7 @@
|
||||||
import * as Backbone from 'backbone';
|
import * as Backbone from 'backbone';
|
||||||
|
|
||||||
import type { GroupV2ChangeType } from './groups';
|
import type { GroupV2ChangeType } from './groups';
|
||||||
import type { BodyRangeType, BodyRangesType } from './types/Util';
|
import type { DraftBodyRangesType, BodyRangesType } from './types/Util';
|
||||||
import type { CallHistoryDetailsFromDiskType } from './types/Calling';
|
import type { CallHistoryDetailsFromDiskType } from './types/Calling';
|
||||||
import type { CustomColorType, ConversationColorType } from './types/Colors';
|
import type { CustomColorType, ConversationColorType } from './types/Colors';
|
||||||
import type { DeviceType } from './textsecure/Types.d';
|
import type { DeviceType } from './textsecure/Types.d';
|
||||||
|
@ -279,7 +279,7 @@ export type ConversationAttributesType = {
|
||||||
firstUnregisteredAt?: number;
|
firstUnregisteredAt?: number;
|
||||||
draftChanged?: boolean;
|
draftChanged?: boolean;
|
||||||
draftAttachments?: Array<AttachmentDraftType>;
|
draftAttachments?: Array<AttachmentDraftType>;
|
||||||
draftBodyRanges?: Array<BodyRangeType>;
|
draftBodyRanges?: DraftBodyRangesType;
|
||||||
draftTimestamp?: number | null;
|
draftTimestamp?: number | null;
|
||||||
hideStory?: boolean;
|
hideStory?: boolean;
|
||||||
inbox_position?: number;
|
inbox_position?: number;
|
||||||
|
|
|
@ -51,7 +51,7 @@ import * as expirationTimer from '../util/expirationTimer';
|
||||||
import { getUserLanguages } from '../util/userLanguages';
|
import { getUserLanguages } from '../util/userLanguages';
|
||||||
|
|
||||||
import type { ReactionType } from '../types/Reactions';
|
import type { ReactionType } from '../types/Reactions';
|
||||||
import { UUID, UUIDKind } from '../types/UUID';
|
import { isValidUuid, UUID, UUIDKind } from '../types/UUID';
|
||||||
import * as reactionUtil from '../reactions/util';
|
import * as reactionUtil from '../reactions/util';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
|
@ -2002,22 +2002,30 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
id,
|
id,
|
||||||
|
|
||||||
attachments: quote.attachments.slice(),
|
attachments: quote.attachments.slice(),
|
||||||
bodyRanges: quote.bodyRanges.map(({ start, length, mentionUuid }) => {
|
bodyRanges: quote.bodyRanges
|
||||||
strictAssert(
|
.map(({ start, length, mentionUuid }) => {
|
||||||
start != null,
|
strictAssert(
|
||||||
'Received quote with a bodyRange.start == null'
|
start != null,
|
||||||
);
|
'Received quote with a bodyRange.start == null'
|
||||||
strictAssert(
|
);
|
||||||
length != null,
|
strictAssert(
|
||||||
'Received quote with a bodyRange.length == null'
|
length != null,
|
||||||
);
|
'Received quote with a bodyRange.length == null'
|
||||||
|
);
|
||||||
|
if (!isValidUuid(mentionUuid)) {
|
||||||
|
log.warn(
|
||||||
|
`copyFromQuotedMessage: invalid mentionUuid ${mentionUuid}`
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start,
|
start,
|
||||||
length,
|
length,
|
||||||
mentionUuid: dropNull(mentionUuid),
|
mentionUuid,
|
||||||
};
|
};
|
||||||
}),
|
})
|
||||||
|
.filter(isNotNil),
|
||||||
|
|
||||||
// Just placeholder values for the fields
|
// Just placeholder values for the fields
|
||||||
referencedMessageNotFound: false,
|
referencedMessageNotFound: false,
|
||||||
|
|
|
@ -35,7 +35,7 @@ export class MentionBlot extends Embed {
|
||||||
const { uuid, title } = node.dataset;
|
const { uuid, title } = node.dataset;
|
||||||
if (uuid === undefined || title === undefined) {
|
if (uuid === undefined || title === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to make MentionBlot with uuid: ${uuid} and title: ${title}`
|
`Failed to make MentionBlot with uuid: ${uuid}, title: ${title}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Delta from 'quill-delta';
|
||||||
import type { LeafBlot, DeltaOperation } from 'quill';
|
import type { LeafBlot, DeltaOperation } from 'quill';
|
||||||
import type Op from 'quill-delta/dist/Op';
|
import type Op from 'quill-delta/dist/Op';
|
||||||
|
|
||||||
import type { BodyRangeType } from '../types/Util';
|
import type { DraftBodyRangeType, DraftBodyRangesType } from '../types/Util';
|
||||||
import type { MentionBlot } from './mentions/blot';
|
import type { MentionBlot } from './mentions/blot';
|
||||||
|
|
||||||
export type MentionBlotValue = {
|
export type MentionBlotValue = {
|
||||||
|
@ -61,8 +61,8 @@ export const getTextFromOps = (ops: Array<DeltaOperation>): string =>
|
||||||
|
|
||||||
export const getTextAndMentionsFromOps = (
|
export const getTextAndMentionsFromOps = (
|
||||||
ops: Array<Op>
|
ops: Array<Op>
|
||||||
): [string, Array<BodyRangeType>] => {
|
): [string, DraftBodyRangesType] => {
|
||||||
const mentions: Array<BodyRangeType> = [];
|
const mentions: Array<DraftBodyRangeType> = [];
|
||||||
|
|
||||||
const text = ops
|
const text = ops
|
||||||
.reduce((acc, op, index) => {
|
.reduce((acc, op, index) => {
|
||||||
|
@ -168,15 +168,17 @@ export const getDeltaToRemoveStaleMentions = (
|
||||||
|
|
||||||
export const insertMentionOps = (
|
export const insertMentionOps = (
|
||||||
incomingOps: Array<Op>,
|
incomingOps: Array<Op>,
|
||||||
bodyRanges: Array<BodyRangeType>
|
bodyRanges: DraftBodyRangesType
|
||||||
): Array<Op> => {
|
): Array<Op> => {
|
||||||
const ops = [...incomingOps];
|
const ops = [...incomingOps];
|
||||||
|
|
||||||
|
const sortableBodyRanges: Array<DraftBodyRangeType> = bodyRanges.slice();
|
||||||
|
|
||||||
// Working backwards through bodyRanges (to avoid offsetting later mentions),
|
// Working backwards through bodyRanges (to avoid offsetting later mentions),
|
||||||
// Shift off the op with the text to the left of the last mention,
|
// Shift off the op with the text to the left of the last mention,
|
||||||
// Insert a mention based on the current bodyRange,
|
// Insert a mention based on the current bodyRange,
|
||||||
// Unshift the mention and surrounding text to leave the ops ready for the next range
|
// Unshift the mention and surrounding text to leave the ops ready for the next range
|
||||||
bodyRanges
|
sortableBodyRanges
|
||||||
.sort((a, b) => b.start - a.start)
|
.sort((a, b) => b.start - a.start)
|
||||||
.forEach(({ start, length, mentionUuid, replacementText }) => {
|
.forEach(({ start, length, mentionUuid, replacementText }) => {
|
||||||
const op = ops.shift();
|
const op = ops.shift();
|
||||||
|
|
|
@ -44,7 +44,7 @@ import type {
|
||||||
ConversationAttributesType,
|
ConversationAttributesType,
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
} from '../../model-types.d';
|
} from '../../model-types.d';
|
||||||
import type { BodyRangeType } from '../../types/Util';
|
import type { DraftBodyRangesType } from '../../types/Util';
|
||||||
import { CallMode } from '../../types/Calling';
|
import { CallMode } from '../../types/Calling';
|
||||||
import type { MediaItemType } from '../../types/MediaItem';
|
import type { MediaItemType } from '../../types/MediaItem';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
|
@ -203,7 +203,7 @@ export type ConversationType = {
|
||||||
|
|
||||||
shouldShowDraft?: boolean;
|
shouldShowDraft?: boolean;
|
||||||
draftText?: string | null;
|
draftText?: string | null;
|
||||||
draftBodyRanges?: Array<BodyRangeType>;
|
draftBodyRanges?: DraftBodyRangesType;
|
||||||
draftPreview?: string;
|
draftPreview?: string;
|
||||||
|
|
||||||
sharedGroupNames: Array<string>;
|
sharedGroupNames: Array<string>;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { isEqual, pick } from 'lodash';
|
||||||
|
|
||||||
import * as Errors from '../../types/errors';
|
import * as Errors from '../../types/errors';
|
||||||
import type { AttachmentType } from '../../types/Attachment';
|
import type { AttachmentType } from '../../types/Attachment';
|
||||||
import type { BodyRangeType } from '../../types/Util';
|
import type { DraftBodyRangesType } from '../../types/Util';
|
||||||
import type { ConversationModel } from '../../models/conversations';
|
import type { ConversationModel } from '../../models/conversations';
|
||||||
import type { MessageAttributesType } from '../../model-types.d';
|
import type { MessageAttributesType } from '../../model-types.d';
|
||||||
import type {
|
import type {
|
||||||
|
@ -500,7 +500,7 @@ function reactToStory(
|
||||||
function replyToStory(
|
function replyToStory(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
messageBody: string,
|
messageBody: string,
|
||||||
mentions: Array<BodyRangeType>,
|
mentions: DraftBodyRangesType,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
story: StoryViewType
|
story: StoryViewType
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
|
|
|
@ -37,7 +37,7 @@ import type { UUIDStringType } from '../../types/UUID';
|
||||||
|
|
||||||
import type { EmbeddedContactType } from '../../types/EmbeddedContact';
|
import type { EmbeddedContactType } from '../../types/EmbeddedContact';
|
||||||
import { embeddedContactSelector } from '../../types/EmbeddedContact';
|
import { embeddedContactSelector } from '../../types/EmbeddedContact';
|
||||||
import type { AssertProps, BodyRangesType } from '../../types/Util';
|
import type { AssertProps, HydratedBodyRangesType } from '../../types/Util';
|
||||||
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||||
import { getMentionsRegex } from '../../types/Message';
|
import { getMentionsRegex } from '../../types/Message';
|
||||||
import { CallMode } from '../../types/Calling';
|
import { CallMode } from '../../types/Calling';
|
||||||
|
@ -289,7 +289,7 @@ export const processBodyRanges = createSelectorCreator(memoizeByRoot, isEqual)(
|
||||||
(
|
(
|
||||||
{ bodyRanges }: Pick<MessageWithUIFieldsType, 'bodyRanges'>,
|
{ bodyRanges }: Pick<MessageWithUIFieldsType, 'bodyRanges'>,
|
||||||
{ conversationSelector }: { conversationSelector: GetConversationByIdType }
|
{ conversationSelector }: { conversationSelector: GetConversationByIdType }
|
||||||
): BodyRangesType | undefined => {
|
): HydratedBodyRangesType | undefined => {
|
||||||
if (!bodyRanges) {
|
if (!bodyRanges) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ export const processBodyRanges = createSelectorCreator(memoizeByRoot, isEqual)(
|
||||||
})
|
})
|
||||||
.sort((a, b) => b.start - a.start);
|
.sort((a, b) => b.start - a.start);
|
||||||
},
|
},
|
||||||
(_, ranges): undefined | BodyRangesType => ranges
|
(_, ranges): undefined | HydratedBodyRangesType => ranges
|
||||||
);
|
);
|
||||||
|
|
||||||
const getAuthorForMessage = createSelectorCreator(memoizeByRoot)(
|
const getAuthorForMessage = createSelectorCreator(memoizeByRoot)(
|
||||||
|
@ -780,7 +780,7 @@ export const getPropsForMessage: (
|
||||||
(
|
(
|
||||||
_,
|
_,
|
||||||
attachments: Array<AttachmentType>,
|
attachments: Array<AttachmentType>,
|
||||||
bodyRanges: BodyRangesType | undefined,
|
bodyRanges: HydratedBodyRangesType | undefined,
|
||||||
author: PropsData['author'],
|
author: PropsData['author'],
|
||||||
previews: Array<LinkPreviewType>,
|
previews: Array<LinkPreviewType>,
|
||||||
reactions: PropsData['reactions'],
|
reactions: PropsData['reactions'],
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
} from './conversations';
|
} from './conversations';
|
||||||
|
|
||||||
import type { BodyRangeType } from '../../types/Util';
|
import type { BodyRangeType, HydratedBodyRangeType } from '../../types/Util';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
|
|
||||||
|
@ -173,14 +173,17 @@ export const getCachedSelectorForMessageSearchResult = createSelector(
|
||||||
conversationId: message.conversationId,
|
conversationId: message.conversationId,
|
||||||
sentAt: message.sent_at,
|
sentAt: message.sent_at,
|
||||||
snippet: message.snippet || '',
|
snippet: message.snippet || '',
|
||||||
bodyRanges: bodyRanges.map((bodyRange: BodyRangeType) => {
|
bodyRanges: bodyRanges.map(
|
||||||
const conversation = conversationSelector(bodyRange.mentionUuid);
|
(bodyRange: BodyRangeType): HydratedBodyRangeType => {
|
||||||
|
const conversation = conversationSelector(bodyRange.mentionUuid);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...bodyRange,
|
...bodyRange,
|
||||||
replacementText: conversation.title,
|
conversationID: conversation.id,
|
||||||
};
|
replacementText: conversation.title,
|
||||||
}),
|
};
|
||||||
|
}
|
||||||
|
),
|
||||||
body: message.body || '',
|
body: message.body || '',
|
||||||
|
|
||||||
isSelected: Boolean(
|
isSelected: Boolean(
|
||||||
|
|
|
@ -296,15 +296,20 @@ export const getStoryReplies = createSelector(
|
||||||
? me
|
? me
|
||||||
: conversationSelector(reply.sourceUuid || reply.source);
|
: conversationSelector(reply.sourceUuid || reply.source);
|
||||||
|
|
||||||
|
const { bodyRanges } = reply;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
author: getAvatarData(conversation),
|
author: getAvatarData(conversation),
|
||||||
...pick(reply, [
|
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
||||||
'body',
|
bodyRanges: bodyRanges?.map(bodyRange => {
|
||||||
'bodyRanges',
|
const mentionConvo = conversationSelector(bodyRange.mentionUuid);
|
||||||
'deletedForEveryone',
|
|
||||||
'id',
|
return {
|
||||||
'timestamp',
|
...bodyRange,
|
||||||
]),
|
conversationID: mentionConvo.id,
|
||||||
|
replacementText: mentionConvo.title,
|
||||||
|
};
|
||||||
|
}),
|
||||||
reactionEmoji: reply.storyReaction?.emoji,
|
reactionEmoji: reply.storyReaction?.emoji,
|
||||||
contactNameColor: contactNameColorSelector(
|
contactNameColor: contactNameColorSelector(
|
||||||
reply.conversationId,
|
reply.conversationId,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { BodyRangeType } from '../../types/Util';
|
import type { DraftBodyRangesType } from '../../types/Util';
|
||||||
import type { ForwardMessagePropsType } from '../ducks/globalModals';
|
import type { ForwardMessagePropsType } from '../ducks/globalModals';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
|
@ -121,7 +121,7 @@ export function SmartForwardMessageModal(): JSX.Element | null {
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onEditorStateChange={(
|
onEditorStateChange={(
|
||||||
messageText: string,
|
messageText: string,
|
||||||
_: Array<BodyRangeType>,
|
_: DraftBodyRangesType,
|
||||||
caretLocation?: number
|
caretLocation?: number
|
||||||
) => {
|
) => {
|
||||||
if (!attachments.length) {
|
if (!attachments.length) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ describe('getTextWithMentions', () => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'abcdef',
|
mentionUuid: 'abcdef',
|
||||||
replacementText: 'fred',
|
replacementText: 'fred',
|
||||||
|
conversationID: 'x',
|
||||||
start: 4,
|
start: 4,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -28,12 +29,14 @@ describe('getTextWithMentions', () => {
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'blarg',
|
mentionUuid: 'blarg',
|
||||||
replacementText: 'jerry',
|
replacementText: 'jerry',
|
||||||
|
conversationID: 'x',
|
||||||
start: 0,
|
start: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
length: 1,
|
length: 1,
|
||||||
mentionUuid: 'abcdef',
|
mentionUuid: 'abcdef',
|
||||||
replacementText: 'fred',
|
replacementText: 'fred',
|
||||||
|
conversationID: 'x',
|
||||||
start: 7,
|
start: 7,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { AttachmentType } from './Attachment';
|
import type { AttachmentType } from './Attachment';
|
||||||
import type { BodyRangesType, LocalizerType } from './Util';
|
import type { HydratedBodyRangesType, LocalizerType } from './Util';
|
||||||
import type { ContactNameColorType } from './Colors';
|
import type { ContactNameColorType } from './Colors';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { ReadStatus } from '../messages/MessageReadStatus';
|
import type { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
|
@ -25,7 +25,7 @@ export type ReplyType = {
|
||||||
| 'title'
|
| 'title'
|
||||||
>;
|
>;
|
||||||
body?: string;
|
body?: string;
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: HydratedBodyRangesType;
|
||||||
contactNameColor?: ContactNameColorType;
|
contactNameColor?: ContactNameColorType;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
deletedForEveryone?: boolean;
|
deletedForEveryone?: boolean;
|
||||||
|
|
|
@ -4,15 +4,31 @@
|
||||||
import type { IntlShape } from 'react-intl';
|
import type { IntlShape } from 'react-intl';
|
||||||
import type { UUIDStringType } from './UUID';
|
import type { UUIDStringType } from './UUID';
|
||||||
|
|
||||||
|
// Cold storage of body ranges
|
||||||
|
|
||||||
export type BodyRangeType = {
|
export type BodyRangeType = {
|
||||||
start: number;
|
start: number;
|
||||||
length: number;
|
length: number;
|
||||||
mentionUuid?: string;
|
mentionUuid: string;
|
||||||
replacementText?: string;
|
|
||||||
conversationID?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BodyRangesType = Array<BodyRangeType>;
|
export type BodyRangesType = ReadonlyArray<BodyRangeType>;
|
||||||
|
|
||||||
|
// Used exclusive in CompositionArea and related conversation_view.tsx calls.
|
||||||
|
|
||||||
|
export type DraftBodyRangeType = BodyRangeType & {
|
||||||
|
replacementText: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DraftBodyRangesType = ReadonlyArray<DraftBodyRangeType>;
|
||||||
|
|
||||||
|
// Fully hydrated body range to be used in UI components.
|
||||||
|
|
||||||
|
export type HydratedBodyRangeType = DraftBodyRangeType & {
|
||||||
|
conversationID: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HydratedBodyRangesType = ReadonlyArray<HydratedBodyRangeType>;
|
||||||
|
|
||||||
export type StoryContextType = {
|
export type StoryContextType = {
|
||||||
authorUuid?: UUIDStringType;
|
authorUuid?: UUIDStringType;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { BodyRangesType } from '../types/Util';
|
import type { DraftBodyRangeType, DraftBodyRangesType } from '../types/Util';
|
||||||
|
|
||||||
export function getTextWithMentions(
|
export function getTextWithMentions(
|
||||||
bodyRanges: BodyRangesType,
|
bodyRanges: DraftBodyRangesType,
|
||||||
text: string
|
text: string
|
||||||
): string {
|
): string {
|
||||||
return bodyRanges
|
const sortableBodyRanges: Array<DraftBodyRangeType> = bodyRanges.slice();
|
||||||
|
return sortableBodyRanges
|
||||||
.sort((a, b) => b.start - a.start)
|
.sort((a, b) => b.start - a.start)
|
||||||
.reduce((acc, { start, length, replacementText }) => {
|
.reduce((acc, { start, length, replacementText }) => {
|
||||||
const left = acc.slice(0, start);
|
const left = acc.slice(0, start);
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { render } from 'mustache';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import { isGIF } from '../types/Attachment';
|
import { isGIF } from '../types/Attachment';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
import type { BodyRangeType, BodyRangesType } from '../types/Util';
|
import type { DraftBodyRangesType } from '../types/Util';
|
||||||
import type { MIMEType } from '../types/MIME';
|
import type { MIMEType } from '../types/MIME';
|
||||||
import type { ConversationModel } from '../models/conversations';
|
import type { ConversationModel } from '../models/conversations';
|
||||||
import type {
|
import type {
|
||||||
|
@ -202,7 +202,7 @@ const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
|
||||||
export class ConversationView extends window.Backbone.View<ConversationModel> {
|
export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
private debouncedSaveDraft: (
|
private debouncedSaveDraft: (
|
||||||
messageText: string,
|
messageText: string,
|
||||||
bodyRanges: Array<BodyRangeType>
|
bodyRanges: DraftBodyRangesType
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
private lazyUpdateVerified: () => void;
|
private lazyUpdateVerified: () => void;
|
||||||
|
|
||||||
|
@ -544,7 +544,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.sendStickerMessage({ packId, stickerId }),
|
this.sendStickerMessage({ packId, stickerId }),
|
||||||
onEditorStateChange: (
|
onEditorStateChange: (
|
||||||
msg: string,
|
msg: string,
|
||||||
bodyRanges: Array<BodyRangeType>,
|
bodyRanges: DraftBodyRangesType,
|
||||||
caretLocation?: number
|
caretLocation?: number
|
||||||
) => this.onEditorStateChange(msg, bodyRanges, caretLocation),
|
) => this.onEditorStateChange(msg, bodyRanges, caretLocation),
|
||||||
onTextTooLong: () => showToast(ToastMessageBodyTooLong),
|
onTextTooLong: () => showToast(ToastMessageBodyTooLong),
|
||||||
|
@ -621,7 +621,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
voiceNoteAttachment,
|
voiceNoteAttachment,
|
||||||
}: {
|
}: {
|
||||||
draftAttachments?: ReadonlyArray<AttachmentType>;
|
draftAttachments?: ReadonlyArray<AttachmentType>;
|
||||||
mentions?: BodyRangesType;
|
mentions?: DraftBodyRangesType;
|
||||||
message?: string;
|
message?: string;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
voiceNoteAttachment?: AttachmentType;
|
voiceNoteAttachment?: AttachmentType;
|
||||||
|
@ -2490,7 +2490,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
async sendMessage(
|
async sendMessage(
|
||||||
message = '',
|
message = '',
|
||||||
mentions: BodyRangesType = [],
|
mentions: DraftBodyRangesType = [],
|
||||||
options: {
|
options: {
|
||||||
draftAttachments?: ReadonlyArray<AttachmentType>;
|
draftAttachments?: ReadonlyArray<AttachmentType>;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
|
@ -2589,7 +2589,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
onEditorStateChange(
|
onEditorStateChange(
|
||||||
messageText: string,
|
messageText: string,
|
||||||
bodyRanges: Array<BodyRangeType>,
|
bodyRanges: DraftBodyRangesType,
|
||||||
caretLocation?: number
|
caretLocation?: number
|
||||||
): void {
|
): void {
|
||||||
this.maybeBumpTyping(messageText);
|
this.maybeBumpTyping(messageText);
|
||||||
|
@ -2605,7 +2605,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
async saveDraft(
|
async saveDraft(
|
||||||
messageText: string,
|
messageText: string,
|
||||||
bodyRanges: Array<BodyRangeType>
|
bodyRanges: DraftBodyRangesType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { model }: { model: ConversationModel } = this;
|
const { model }: { model: ConversationModel } = this;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue