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 classNames from 'classnames';
|
||||
import type {
|
||||
BodyRangeType,
|
||||
BodyRangesType,
|
||||
DraftBodyRangesType,
|
||||
LocalizerType,
|
||||
ThemeType,
|
||||
} from '../types/Util';
|
||||
|
@ -116,7 +115,7 @@ export type OwnProps = Readonly<{
|
|||
onSelectMediaQuality(isHQ: boolean): unknown;
|
||||
onSendMessage(options: {
|
||||
draftAttachments?: ReadonlyArray<AttachmentDraftType>;
|
||||
mentions?: BodyRangesType;
|
||||
mentions?: DraftBodyRangesType;
|
||||
message?: string;
|
||||
timestamp?: number;
|
||||
voiceNoteAttachment?: InMemoryAttachmentDraftType;
|
||||
|
@ -276,7 +275,7 @@ export const CompositionArea = ({
|
|||
}, [inputApiRef, setLarge]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(message: string, mentions: Array<BodyRangeType>, timestamp: number) => {
|
||||
(message: string, mentions: DraftBodyRangesType, timestamp: number) => {
|
||||
emojiButtonRef.current?.close();
|
||||
onSendMessage({
|
||||
draftAttachments,
|
||||
|
|
|
@ -14,7 +14,11 @@ import { MentionCompletion } from '../quill/mentions/completion';
|
|||
import { EmojiBlot, EmojiCompletion } from '../quill/emoji';
|
||||
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||
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 { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||
import { isValidUuid } from '../types/UUID';
|
||||
|
@ -71,7 +75,7 @@ export type Props = Readonly<{
|
|||
inputApi?: React.MutableRefObject<InputApi | undefined>;
|
||||
skinTone?: EmojiPickDataType['skinTone'];
|
||||
draftText?: string;
|
||||
draftBodyRanges?: Array<BodyRangeType>;
|
||||
draftBodyRanges?: DraftBodyRangesType;
|
||||
moduleClassName?: string;
|
||||
theme: ThemeType;
|
||||
placeholder?: string;
|
||||
|
@ -80,14 +84,14 @@ export type Props = Readonly<{
|
|||
onDirtyChange?(dirty: boolean): unknown;
|
||||
onEditorStateChange?(
|
||||
messageText: string,
|
||||
bodyRanges: Array<BodyRangeType>,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
): unknown;
|
||||
onTextTooLong(): unknown;
|
||||
onPickEmoji(o: EmojiPickDataType): unknown;
|
||||
onSubmit(
|
||||
message: string,
|
||||
mentions: Array<BodyRangeType>,
|
||||
mentions: DraftBodyRangesType,
|
||||
timestamp: number
|
||||
): unknown;
|
||||
onScroll?: (ev: React.UIEvent<HTMLElement>) => void;
|
||||
|
@ -143,7 +147,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
|||
|
||||
const generateDelta = (
|
||||
text: string,
|
||||
bodyRanges: Array<BodyRangeType>
|
||||
bodyRanges: DraftBodyRangesType
|
||||
): Delta => {
|
||||
const initialOps = [{ insert: text }];
|
||||
const opsWithMentions = insertMentionOps(initialOps, bodyRanges);
|
||||
|
@ -152,7 +156,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
|||
return new Delta(opsWithEmojis);
|
||||
};
|
||||
|
||||
const getTextAndMentions = (): [string, Array<BodyRangeType>] => {
|
||||
const getTextAndMentions = (): [string, DraftBodyRangesType] => {
|
||||
const quill = quillRef.current;
|
||||
|
||||
if (quill === undefined) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { shouldNeverBeCalled } from '../util/shouldNeverBeCalled';
|
|||
import type { InputApi } from './CompositionInput';
|
||||
import { CompositionInput } from './CompositionInput';
|
||||
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 { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||
import * as grapheme from '../util/grapheme';
|
||||
|
@ -24,13 +24,13 @@ export type CompositionTextAreaProps = {
|
|||
onPickEmoji: (e: EmojiPickDataType) => void;
|
||||
onChange: (
|
||||
messageText: string,
|
||||
bodyRanges: Array<BodyRangeType>,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number | undefined
|
||||
) => void;
|
||||
onSetSkinTone: (tone: number) => void;
|
||||
onSubmit: (
|
||||
message: string,
|
||||
mentions: Array<BodyRangeType>,
|
||||
mentions: DraftBodyRangesType,
|
||||
timestamp: number
|
||||
) => void;
|
||||
onTextTooLong: () => void;
|
||||
|
@ -88,7 +88,7 @@ export const CompositionTextArea = ({
|
|||
const handleChange = React.useCallback(
|
||||
(
|
||||
newValue: string,
|
||||
bodyRanges: Array<BodyRangeType>,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number | undefined
|
||||
) => {
|
||||
const inputEl = inputApiRef.current;
|
||||
|
|
|
@ -24,7 +24,11 @@ import { ConversationList, RowType } from './ConversationList';
|
|||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||
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 { ModalHost } from './ModalHost';
|
||||
import { SearchInput } from './SearchInput';
|
||||
|
@ -54,7 +58,7 @@ export type DataPropsType = {
|
|||
onClose: () => void;
|
||||
onEditorStateChange: (
|
||||
messageText: string,
|
||||
bodyRanges: Array<BodyRangeType>,
|
||||
bodyRanges: DraftBodyRangesType,
|
||||
caretLocation?: number
|
||||
) => unknown;
|
||||
theme: ThemeType;
|
||||
|
|
|
@ -27,13 +27,11 @@ export type Props = {
|
|||
renderText?: RenderTextCallbackType;
|
||||
};
|
||||
|
||||
export class Intl extends React.Component<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
renderText: ({ text, key }) => (
|
||||
<React.Fragment key={key}>{text}</React.Fragment>
|
||||
),
|
||||
};
|
||||
const defaultRenderText: RenderTextCallbackType = ({ text, key }) => (
|
||||
<React.Fragment key={key}>{text}</React.Fragment>
|
||||
);
|
||||
|
||||
export class Intl extends React.Component<Props> {
|
||||
public getComponent(
|
||||
index: number,
|
||||
placeholderName: string,
|
||||
|
@ -74,7 +72,7 @@ export class Intl extends React.Component<Props> {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
public override render() {
|
||||
const { components, id, i18n, renderText } = this.props;
|
||||
const { components, id, i18n, renderText = defaultRenderText } = this.props;
|
||||
|
||||
if (!id) {
|
||||
log.error('Error: Intl id prop not provided');
|
||||
|
@ -96,12 +94,6 @@ export class Intl extends React.Component<Props> {
|
|||
> = [];
|
||||
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) {
|
||||
throw new Error(
|
||||
'Array syntax is not supported with more than one placeholder'
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, {
|
|||
useState,
|
||||
} from 'react';
|
||||
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 { ConversationType } from '../state/ducks/conversations';
|
||||
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||
|
@ -80,7 +80,7 @@ export type PropsType = {
|
|||
onReactToStory: (emoji: string, story: StoryViewType) => unknown;
|
||||
onReplyToStory: (
|
||||
message: string,
|
||||
mentions: Array<BodyRangeType>,
|
||||
mentions: DraftBodyRangesType,
|
||||
timestamp: number,
|
||||
story: StoryViewType
|
||||
) => unknown;
|
||||
|
|
|
@ -10,8 +10,10 @@ import React, {
|
|||
} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { usePopper } from 'react-popper';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
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 { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||
import type { InputApi } from './CompositionInput';
|
||||
|
@ -56,7 +58,8 @@ const MESSAGE_DEFAULT_PROPS = {
|
|||
markAttachmentAsCorrupted: shouldNeverBeCalled,
|
||||
markViewed: shouldNeverBeCalled,
|
||||
messageExpanded: shouldNeverBeCalled,
|
||||
openConversation: shouldNeverBeCalled,
|
||||
// Called when clicking mention, but shouldn't do anything.
|
||||
openConversation: noop,
|
||||
openGiftBadge: shouldNeverBeCalled,
|
||||
openLink: shouldNeverBeCalled,
|
||||
previews: [],
|
||||
|
@ -90,7 +93,7 @@ export type PropsType = {
|
|||
onReact: (emoji: string) => unknown;
|
||||
onReply: (
|
||||
message: string,
|
||||
mentions: Array<BodyRangeType>,
|
||||
mentions: DraftBodyRangesType,
|
||||
timestamp: number
|
||||
) => unknown;
|
||||
onSetSkinTone: (tone: number) => unknown;
|
||||
|
@ -315,6 +318,7 @@ export const StoryViewsNRepliesModal = ({
|
|||
key={reply.id}
|
||||
i18n={i18n}
|
||||
reply={reply}
|
||||
reactionEmoji={reply.reactionEmoji}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
/>
|
||||
) : (
|
||||
|
@ -504,12 +508,14 @@ export const StoryViewsNRepliesModal = ({
|
|||
type ReactionProps = {
|
||||
i18n: LocalizerType;
|
||||
reply: ReplyType;
|
||||
reactionEmoji: string;
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
};
|
||||
|
||||
const Reaction = ({
|
||||
i18n,
|
||||
reply,
|
||||
reactionEmoji,
|
||||
getPreferredBadge,
|
||||
}: ReactionProps): JSX.Element => {
|
||||
// TODO: DESKTOP-4503 - reactions delete/doe
|
||||
|
@ -546,7 +552,7 @@ const Reaction = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Emojify text={reply.reactionEmoji} />
|
||||
<Emojify text={reactionEmoji} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,27 +11,18 @@ export type Props = {
|
|||
renderNonNewLine?: RenderTextCallbackType;
|
||||
};
|
||||
|
||||
export class AddNewLines extends React.Component<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
renderNonNewLine: ({ text }) => text,
|
||||
};
|
||||
const defaultRenderNonNewLine: RenderTextCallbackType = ({ text }) => text;
|
||||
|
||||
export class AddNewLines extends React.Component<Props> {
|
||||
public override render():
|
||||
| JSX.Element
|
||||
| string
|
||||
| null
|
||||
| Array<JSX.Element | string | null> {
|
||||
const { text, renderNonNewLine } = this.props;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const results: Array<any> = [];
|
||||
const { text, renderNonNewLine = defaultRenderNonNewLine } = this.props;
|
||||
const results: Array<JSX.Element | string> = [];
|
||||
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 last = 0;
|
||||
let count = 1;
|
||||
|
|
|
@ -43,18 +43,21 @@ export const MultipleMentions = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: 'abc',
|
||||
replacementText: 'Professor Farnsworth',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 2,
|
||||
length: 1,
|
||||
mentionUuid: 'def',
|
||||
replacementText: 'Philip J Fry',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
length: 1,
|
||||
mentionUuid: 'xyz',
|
||||
replacementText: 'Yancy Fry',
|
||||
conversationID: 'x',
|
||||
},
|
||||
];
|
||||
const props = createProps({
|
||||
|
@ -77,18 +80,21 @@ export const ComplexMentions = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: 'ioe',
|
||||
replacementText: 'Cereal Killer',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 78,
|
||||
length: 1,
|
||||
mentionUuid: 'fdr',
|
||||
replacementText: 'Acid Burn',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 4,
|
||||
length: 1,
|
||||
mentionUuid: 'ope',
|
||||
replacementText: 'Zero Cool',
|
||||
conversationID: 'x',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
import React from 'react';
|
||||
import { sortBy } from 'lodash';
|
||||
import { Emojify } from './Emojify';
|
||||
import type { BodyRangesType } from '../../types/Util';
|
||||
import type {
|
||||
BodyRangesType,
|
||||
HydratedBodyRangeType,
|
||||
HydratedBodyRangesType,
|
||||
} from '../../types/Util';
|
||||
|
||||
export type Props = {
|
||||
bodyRanges?: BodyRangesType;
|
||||
bodyRanges?: HydratedBodyRangesType;
|
||||
direction?: 'incoming' | 'outgoing';
|
||||
openConversation?: (conversationId: string, messageId?: string) => void;
|
||||
text: string;
|
||||
|
@ -28,7 +32,7 @@ export const AtMentionify = ({
|
|||
let match = MENTIONS_REGEX.exec(text);
|
||||
let last = 0;
|
||||
|
||||
const rangeStarts = new Map();
|
||||
const rangeStarts = new Map<number, HydratedBodyRangeType>();
|
||||
bodyRanges.forEach(range => {
|
||||
rangeStarts.set(range.start, range);
|
||||
});
|
||||
|
@ -49,7 +53,7 @@ export const AtMentionify = ({
|
|||
className={`MessageBody__at-mention MessageBody__at-mention--${direction}`}
|
||||
key={range.start}
|
||||
onClick={() => {
|
||||
if (openConversation && range.conversationID) {
|
||||
if (openConversation) {
|
||||
openConversation(range.conversationID);
|
||||
}
|
||||
}}
|
||||
|
@ -57,8 +61,7 @@ export const AtMentionify = ({
|
|||
if (
|
||||
e.target === e.currentTarget &&
|
||||
e.keyCode === 13 &&
|
||||
openConversation &&
|
||||
range.conversationID
|
||||
openConversation
|
||||
) {
|
||||
openConversation(range.conversationID);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export const ChangeNumberNotification: React.FC<Props> = props => {
|
|||
<Intl
|
||||
id="ChangeNumber--notification"
|
||||
components={{
|
||||
sender: <Emojify text={sender.title || sender.firstName} />,
|
||||
sender: <Emojify text={sender.title || sender.firstName || ''} />,
|
||||
}}
|
||||
i18n={i18n}
|
||||
/>
|
||||
|
|
|
@ -308,6 +308,22 @@ export const ContactSpoofingReviewDialog: FunctionComponent<
|
|||
conversationInfo.conversation.profileName ||
|
||||
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 (
|
||||
<>
|
||||
{index !== 0 && <hr />}
|
||||
|
@ -318,18 +334,7 @@ export const ContactSpoofingReviewDialog: FunctionComponent<
|
|||
i18n={i18n}
|
||||
theme={theme}
|
||||
>
|
||||
{Boolean(oldName) && oldName !== newName && (
|
||||
<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>
|
||||
)}
|
||||
{callout}
|
||||
{button && (
|
||||
<div className="module-ContactSpoofingReviewDialog__buttons">
|
||||
{button}
|
||||
|
|
|
@ -49,19 +49,15 @@ export type Props = {
|
|||
renderNonEmoji?: RenderTextCallbackType;
|
||||
};
|
||||
|
||||
const defaultRenderNonEmoji: RenderTextCallbackType = ({ text }) => text;
|
||||
|
||||
export class Emojify extends React.Component<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
renderNonEmoji: ({ text }) => text,
|
||||
};
|
||||
|
||||
public override render(): null | Array<JSX.Element | string | null> {
|
||||
const { text, sizeClass, renderNonEmoji } = this.props;
|
||||
|
||||
// We have to do this, because renderNonEmoji is not required in our Props object,
|
||||
// but it is always provided via defaultProps.
|
||||
if (!renderNonEmoji) {
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
text,
|
||||
sizeClass,
|
||||
renderNonEmoji = defaultRenderNonEmoji,
|
||||
} = this.props;
|
||||
|
||||
return splitByEmoji(text).map(({ type, value: match }, index) => {
|
||||
if (type === 'emoji') {
|
||||
|
|
|
@ -321,28 +321,20 @@ export type Props = {
|
|||
|
||||
const SUPPORTED_PROTOCOLS = /^(http|https):/i;
|
||||
|
||||
export class Linkify extends React.Component<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
renderNonLink: ({ text }) => text,
|
||||
};
|
||||
const defaultRenderNonLink: RenderTextCallbackType = ({ text }) => text;
|
||||
|
||||
export class Linkify extends React.Component<Props> {
|
||||
public override render():
|
||||
| JSX.Element
|
||||
| string
|
||||
| null
|
||||
| Array<JSX.Element | string | null> {
|
||||
const { text, renderNonLink } = this.props;
|
||||
const { text, renderNonLink = defaultRenderNonLink } = this.props;
|
||||
|
||||
if (!shouldLinkifyMessage(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<{
|
||||
chunk: string;
|
||||
matchData: ReadonlyArray<LinkifyIt.Match>;
|
||||
|
|
|
@ -63,7 +63,7 @@ import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
|
|||
import { isFileDangerous } from '../../util/isFileDangerous';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import type {
|
||||
BodyRangesType,
|
||||
HydratedBodyRangesType,
|
||||
LocalizerType,
|
||||
ThemeType,
|
||||
} from '../../types/Util';
|
||||
|
@ -228,7 +228,7 @@ export type PropsData = {
|
|||
authorProfileName?: string;
|
||||
authorTitle: string;
|
||||
authorName?: string;
|
||||
bodyRanges?: BodyRangesType;
|
||||
bodyRanges?: HydratedBodyRangesType;
|
||||
referencedMessageNotFound: boolean;
|
||||
isViewOnce: boolean;
|
||||
isGiftBadge: boolean;
|
||||
|
@ -261,7 +261,7 @@ export type PropsData = {
|
|||
canDeleteForEveryone: boolean;
|
||||
isBlocked: boolean;
|
||||
isMessageRequestAccepted: boolean;
|
||||
bodyRanges?: BodyRangesType;
|
||||
bodyRanges?: HydratedBodyRangesType;
|
||||
|
||||
menu: JSX.Element | undefined;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
|
|
|
@ -114,6 +114,7 @@ export const Mention = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: 'tuv',
|
||||
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',
|
||||
|
@ -135,18 +136,21 @@ export const MultipleMentions = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: 'def',
|
||||
replacementText: 'Philip J Fry',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 4,
|
||||
length: 1,
|
||||
mentionUuid: 'abc',
|
||||
replacementText: 'Professor Farnsworth',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
length: 1,
|
||||
mentionUuid: 'xyz',
|
||||
replacementText: 'Yancy Fry',
|
||||
conversationID: 'x',
|
||||
},
|
||||
],
|
||||
text: '\uFFFC \uFFFC \uFFFC',
|
||||
|
@ -168,18 +172,21 @@ export const ComplexMessageBody = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: 'wer',
|
||||
replacementText: 'Acid Burn',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 80,
|
||||
length: 1,
|
||||
mentionUuid: 'xox',
|
||||
replacementText: 'Cereal Killer',
|
||||
conversationID: 'x',
|
||||
},
|
||||
{
|
||||
start: 4,
|
||||
length: 1,
|
||||
mentionUuid: 'ldo',
|
||||
replacementText: 'Zero Cool',
|
||||
conversationID: 'x',
|
||||
},
|
||||
],
|
||||
direction: 'outgoing',
|
||||
|
|
|
@ -14,7 +14,7 @@ import { AddNewLines } from './AddNewLines';
|
|||
import { Linkify } from './Linkify';
|
||||
|
||||
import type {
|
||||
BodyRangesType,
|
||||
HydratedBodyRangesType,
|
||||
LocalizerType,
|
||||
RenderTextCallbackType,
|
||||
} from '../../types/Util';
|
||||
|
@ -34,7 +34,7 @@ export type Props = {
|
|||
/** If set, links will be left alone instead of turned into clickable `<a>` tags. */
|
||||
disableLinks?: boolean;
|
||||
i18n: LocalizerType;
|
||||
bodyRanges?: BodyRangesType;
|
||||
bodyRanges?: HydratedBodyRangesType;
|
||||
onIncreaseTextLength?: () => unknown;
|
||||
openConversation?: OpenConversationActionType;
|
||||
kickOffBodyDownload?: () => void;
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as GoogleChrome from '../../util/GoogleChrome';
|
|||
|
||||
import { MessageBody } from './MessageBody';
|
||||
import type { AttachmentType, ThumbnailType } from '../../types/Attachment';
|
||||
import type { BodyRangesType, LocalizerType } from '../../types/Util';
|
||||
import type { HydratedBodyRangesType, LocalizerType } from '../../types/Util';
|
||||
import type {
|
||||
ConversationColorType,
|
||||
CustomColorType,
|
||||
|
@ -27,7 +27,7 @@ export type Props = {
|
|||
authorTitle: string;
|
||||
conversationColor: ConversationColorType;
|
||||
customColor?: CustomColorType;
|
||||
bodyRanges?: BodyRangesType;
|
||||
bodyRanges?: HydratedBodyRangesType;
|
||||
i18n: LocalizerType;
|
||||
isFromMe: boolean;
|
||||
isIncoming?: boolean;
|
||||
|
|
|
@ -1606,6 +1606,7 @@ Mentions.args = {
|
|||
length: 1,
|
||||
mentionUuid: 'zap',
|
||||
replacementText: 'Zapp Brannigan',
|
||||
conversationID: 'x',
|
||||
},
|
||||
],
|
||||
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> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
shouldShowSeparator: true,
|
||||
};
|
||||
|
||||
public override render(): JSX.Element {
|
||||
const { shouldShowSeparator } = this.props;
|
||||
const { shouldShowSeparator = true } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -52,6 +52,7 @@ export const TwoReplacementsWithAnMention = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: '0ca40892-7b1a-11eb-9439-0242ac130002',
|
||||
replacementText: 'Jin Sakai',
|
||||
conversationID: 'x',
|
||||
start: 33,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -13,7 +13,7 @@ import { AddNewLines } from '../conversation/AddNewLines';
|
|||
import type { SizeClassType } from '../emoji/lib';
|
||||
|
||||
import type {
|
||||
BodyRangesType,
|
||||
HydratedBodyRangesType,
|
||||
LocalizerType,
|
||||
RenderTextCallbackType,
|
||||
} from '../../types/Util';
|
||||
|
@ -21,7 +21,7 @@ import type {
|
|||
const CLASS_NAME = `${MESSAGE_TEXT_CLASS_NAME}__message-search-result-contents`;
|
||||
|
||||
export type Props = {
|
||||
bodyRanges: BodyRangesType;
|
||||
bodyRanges: HydratedBodyRangesType;
|
||||
text: string;
|
||||
i18n: LocalizerType;
|
||||
};
|
||||
|
|
|
@ -206,12 +206,14 @@ export const Mention = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||
replacementText: 'Shoe',
|
||||
conversationID: 'x',
|
||||
start: 113,
|
||||
},
|
||||
{
|
||||
length: 1,
|
||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||
replacementText: 'Shoe',
|
||||
conversationID: 'x',
|
||||
start: 237,
|
||||
},
|
||||
],
|
||||
|
@ -236,6 +238,7 @@ export const MentionRegexp = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||
replacementText: 'RegExp',
|
||||
conversationID: 'x',
|
||||
start: 0,
|
||||
},
|
||||
],
|
||||
|
@ -260,6 +263,7 @@ export const MentionNoMatches = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||
replacementText: 'Neo',
|
||||
conversationID: 'x',
|
||||
start: 0,
|
||||
},
|
||||
],
|
||||
|
@ -283,12 +287,14 @@ export const _MentionNoMatches = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||
replacementText: 'Shoe',
|
||||
conversationID: 'x',
|
||||
start: 113,
|
||||
},
|
||||
{
|
||||
length: 1,
|
||||
mentionUuid: '7d007e95-771d-43ad-9191-eaa86c773cb8',
|
||||
replacementText: 'Shoe',
|
||||
conversationID: 'x',
|
||||
start: 237,
|
||||
},
|
||||
],
|
||||
|
@ -313,12 +319,14 @@ export const DoubleMention = (): JSX.Element => {
|
|||
length: 1,
|
||||
mentionUuid: '9eb2eb65-992a-4909-a2a5-18c56bd7648f',
|
||||
replacementText: 'Alice',
|
||||
conversationID: 'x',
|
||||
start: 4,
|
||||
},
|
||||
{
|
||||
length: 1,
|
||||
mentionUuid: '755ec61b-1590-48da-b003-3e57b2b54448',
|
||||
replacementText: 'Bob',
|
||||
conversationID: 'x',
|
||||
start: 6,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ContactName } from '../conversation/ContactName';
|
|||
|
||||
import { assertDev } from '../../util/assert';
|
||||
import type {
|
||||
BodyRangesType,
|
||||
HydratedBodyRangesType,
|
||||
LocalizerType,
|
||||
ThemeType,
|
||||
} from '../../types/Util';
|
||||
|
@ -31,7 +31,7 @@ export type PropsDataType = {
|
|||
|
||||
snippet: string;
|
||||
body: string;
|
||||
bodyRanges: BodyRangesType;
|
||||
bodyRanges: HydratedBodyRangesType;
|
||||
|
||||
from: Pick<
|
||||
ConversationType,
|
||||
|
@ -82,8 +82,8 @@ const renderPerson = (
|
|||
function getFilteredBodyRanges(
|
||||
snippet: string,
|
||||
body: string,
|
||||
bodyRanges: BodyRangesType
|
||||
): BodyRangesType {
|
||||
bodyRanges: HydratedBodyRangesType
|
||||
): HydratedBodyRangesType {
|
||||
if (!bodyRanges.length) {
|
||||
return [];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue