Reintroduce inline metadata with full RTL support
This commit is contained in:
parent
801c70b298
commit
bb066d4a84
10 changed files with 185 additions and 9 deletions
|
@ -25,6 +25,7 @@ import {
|
|||
MessageBodyReadMore,
|
||||
} from './MessageBodyReadMore';
|
||||
import { MessageMetadata } from './MessageMetadata';
|
||||
import { MessageTextMetadataSpacer } from './MessageTextMetadataSpacer';
|
||||
import { ImageGrid } from './ImageGrid';
|
||||
import { GIF } from './GIF';
|
||||
import { Image } from './Image';
|
||||
|
@ -91,6 +92,19 @@ type Trigger = {
|
|||
handleContextClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
};
|
||||
|
||||
const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 10;
|
||||
const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18;
|
||||
const GUESS_METADATA_WIDTH_OUTGOING_SIZE: Record<MessageStatusType, number> = {
|
||||
delivered: 24,
|
||||
error: 24,
|
||||
paused: 18,
|
||||
'partial-sent': 24,
|
||||
read: 24,
|
||||
sending: 18,
|
||||
sent: 24,
|
||||
viewed: 24,
|
||||
};
|
||||
|
||||
const EXPIRATION_CHECK_MINIMUM = 2000;
|
||||
const EXPIRED_DELAY = 600;
|
||||
const GROUP_AVATAR_SIZE = AvatarSize.TWENTY_EIGHT;
|
||||
|
@ -109,9 +123,17 @@ const SENT_STATUSES = new Set<MessageStatusType>([
|
|||
enum MetadataPlacement {
|
||||
NotRendered,
|
||||
RenderedByMessageAudioComponent,
|
||||
InlineWithText,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
export enum TextDirection {
|
||||
LeftToRight = 'LeftToRight',
|
||||
RightToLeft = 'RightToLeft',
|
||||
Default = 'Default',
|
||||
None = 'None',
|
||||
}
|
||||
|
||||
export const MessageStatuses = [
|
||||
'delivered',
|
||||
'error',
|
||||
|
@ -161,6 +183,7 @@ export type PropsData = {
|
|||
conversationId: string;
|
||||
displayLimit?: number;
|
||||
text?: string;
|
||||
textDirection: TextDirection;
|
||||
textPending?: boolean;
|
||||
isSticker?: boolean;
|
||||
isSelected?: boolean;
|
||||
|
@ -318,6 +341,8 @@ export type Props = PropsData &
|
|||
Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
||||
|
||||
type State = {
|
||||
metadataWidth: number;
|
||||
|
||||
expiring: boolean;
|
||||
expired: boolean;
|
||||
imageBroken: boolean;
|
||||
|
@ -355,6 +380,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
metadataWidth: this.guessMetadataWidth(),
|
||||
|
||||
expiring: false,
|
||||
expired: false,
|
||||
imageBroken: false,
|
||||
|
@ -529,8 +556,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
expirationTimestamp,
|
||||
status,
|
||||
text,
|
||||
textDirection,
|
||||
}: Readonly<Props> = this.props
|
||||
): MetadataPlacement {
|
||||
const isRTL = textDirection === TextDirection.RightToLeft;
|
||||
|
||||
if (
|
||||
!expirationLength &&
|
||||
!expirationTimestamp &&
|
||||
|
@ -550,7 +580,37 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return MetadataPlacement.Bottom;
|
||||
}
|
||||
|
||||
return MetadataPlacement.Bottom;
|
||||
if (isRTL) {
|
||||
return MetadataPlacement.Bottom;
|
||||
}
|
||||
|
||||
return MetadataPlacement.InlineWithText;
|
||||
}
|
||||
|
||||
/**
|
||||
* A lot of the time, we add an invisible inline spacer for messages. This spacer is the
|
||||
* same size as the message metadata. Unfortunately, we don't know how wide it is until
|
||||
* we render it.
|
||||
*
|
||||
* This will probably guess wrong, but it's valuable to get close to the real value
|
||||
* because it can reduce layout jumpiness.
|
||||
*/
|
||||
private guessMetadataWidth(): number {
|
||||
const { direction, expirationLength, expirationTimestamp, status } =
|
||||
this.props;
|
||||
|
||||
let result = GUESS_METADATA_WIDTH_TIMESTAMP_SIZE;
|
||||
|
||||
const hasExpireTimer = Boolean(expirationLength && expirationTimestamp);
|
||||
if (hasExpireTimer) {
|
||||
result += GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE;
|
||||
}
|
||||
|
||||
if (direction === 'outgoing' && status) {
|
||||
result += GUESS_METADATA_WIDTH_OUTGOING_SIZE[status];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public startSelectedTimer(): void {
|
||||
|
@ -672,13 +732,31 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private updateMetadataWidth = (newMetadataWidth: number): void => {
|
||||
this.setState(({ metadataWidth }) => ({
|
||||
// We don't want text to jump around if the metadata shrinks, but we want to make
|
||||
// sure we have enough room.
|
||||
metadataWidth: Math.max(metadataWidth, newMetadataWidth),
|
||||
}));
|
||||
};
|
||||
|
||||
private renderMetadata(): ReactNode {
|
||||
let isInline: boolean;
|
||||
const metadataPlacement = this.getMetadataPlacement();
|
||||
if (
|
||||
metadataPlacement === MetadataPlacement.NotRendered ||
|
||||
metadataPlacement === MetadataPlacement.RenderedByMessageAudioComponent
|
||||
) {
|
||||
return null;
|
||||
switch (metadataPlacement) {
|
||||
case MetadataPlacement.NotRendered:
|
||||
case MetadataPlacement.RenderedByMessageAudioComponent:
|
||||
return null;
|
||||
case MetadataPlacement.InlineWithText:
|
||||
isInline = true;
|
||||
break;
|
||||
case MetadataPlacement.Bottom:
|
||||
isInline = false;
|
||||
break;
|
||||
default:
|
||||
log.error(missingCaseError(metadataPlacement));
|
||||
isInline = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -708,9 +786,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
hasText={Boolean(text)}
|
||||
i18n={i18n}
|
||||
id={id}
|
||||
isInline={isInline}
|
||||
isShowingImage={this.isShowingImage()}
|
||||
isSticker={isStickerLike}
|
||||
isTapToViewExpired={isTapToViewExpired}
|
||||
onWidthMeasured={isInline ? this.updateMetadataWidth : undefined}
|
||||
showMessageDetail={showMessageDetail}
|
||||
status={status}
|
||||
textPending={textPending}
|
||||
|
@ -1378,8 +1458,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
openConversation,
|
||||
status,
|
||||
text,
|
||||
textDirection,
|
||||
textPending,
|
||||
} = this.props;
|
||||
const { metadataWidth } = this.state;
|
||||
const isRTL = textDirection === TextDirection.RightToLeft;
|
||||
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const contents = deletedForEveryone
|
||||
|
@ -1401,7 +1484,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
? 'module-message__text--error'
|
||||
: null
|
||||
)}
|
||||
dir="auto"
|
||||
dir={isRTL ? 'rtl' : undefined}
|
||||
>
|
||||
<MessageBodyReadMore
|
||||
bodyRanges={bodyRanges}
|
||||
|
@ -1415,6 +1498,10 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
text={contents || ''}
|
||||
textPending={textPending}
|
||||
/>
|
||||
{!isRTL &&
|
||||
this.getMetadataPlacement() === MetadataPlacement.InlineWithText && (
|
||||
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue