Skip to content

feat: add message edited timestamp #2304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions docusaurus/docs/React/components/contexts/component-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ Custom UI component to display attachment in an individual message.

Custom UI component to display a attachment previews in `MessageInput`.

| Type | Default |
| --------- | ----------------------------------------------------------------------------------- |
| Type | Default |
| --------- | ---------------------------------------------------------------------------------------------- |
| component | <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> |

### AutocompleteSuggestionHeader
Expand Down Expand Up @@ -256,12 +256,14 @@ Custom UI component to display system messages.

### MessageTimestamp

Custom UI component to display a timestamp on a message.
Custom UI component to display a timestamp on a message. This does not include a timestamp for edited messages.

| Type | Default |
| --------- | ------------------------------------------------------------------------------- |
| component | <GHComponentLink text='MessageTimestamp' path='/Message/MessageTimestamp.tsx'/> |

See also [`Timestamp`](#timestamp).

### MessageBouncePrompt

Custom UI component for the content of the modal dialog for messages that got bounced by the moderation rules.
Expand Down Expand Up @@ -358,6 +360,14 @@ Custom UI component to display the start of a threaded `MessageList`.
| --------- | ---------------------------------------------------------------------- |
| component | <GHComponentLink text='DefaultThreadStart' path='/Thread/Thread.tsx'/> |

### Timestamp

Custom UI component to display a date used in timestamps. It's used internally by the default `MessageTimestamp`, and to display a timestamp for edited messages.

| Type | Default |
| --------- | ----------------------------------------------------------------- |
| component | <GHComponentLink text='Timestamp' path='/Message/Timestamp.tsx'/> |

### TriggerProvider

Optional context provider that lets you override the default autocomplete triggers.
Expand Down
26 changes: 18 additions & 8 deletions docusaurus/docs/React/components/core-components/channel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ Custom UI component to display a message attachment.

Custom UI component to display an attachment previews in `MessageInput`.

| Type | Default |
| --------- | ----------------------------------------------------------------------------------- |
| Type | Default |
| --------- | ---------------------------------------------------------------------------------------------- |
| component | <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> |

### AutocompleteSuggestionHeader
Expand Down Expand Up @@ -384,8 +384,8 @@ Custom UI component to render at the top of the `MessageList`.

A custom function to provide size configuration for image attachments

| Type |
| ---------------------------------------------------------------- |
| Type |
| ----------------------------------------------------------------- |
| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfiguration` |

### initializeOnMount
Expand Down Expand Up @@ -445,7 +445,7 @@ Configuration parameter to mark the active channel as read when mounted (opened)

| Type | Default |
| ------- | ------- |
| boolean | true |
| boolean | true |

### Input

Expand All @@ -459,8 +459,8 @@ Custom UI component handling how the message input is rendered.

Custom component to render link previews in `MessageInput`.

| Type | Default |
| --------- | ----------------------------------------------------------------------------------- |
| Type | Default |
| --------- | ---------------------------------------------------------------------------------- |
| component | <GHComponentLink text='LinkPreviewList' path='/MessageInput/LinkPreviewList.tsx'/> |

### LoadingErrorIndicator
Expand Down Expand Up @@ -553,12 +553,14 @@ Custom UI component to display system messages.

### MessageTimestamp

Custom UI component to display a timestamp on a message.
Custom UI component to display a timestamp on a message. This does not include a timestamp for edited messages.

| Type | Default |
| --------- | ------------------------------------------------------------------------------- |
| component | <GHComponentLink text='MessageTimestamp' path='/Message/MessageTimestamp.tsx'/> |

See also [`Timestamp`](#timestamp).

### MessageBouncePrompt

Custom UI component for the content of the modal dialog for messages that got bounced by the moderation rules.
Expand Down Expand Up @@ -703,6 +705,14 @@ Custom UI component to display the start of a threaded `MessageList`.
| --------- | ---------------------------------------------------------------------- |
| component | <GHComponentLink text='DefaultThreadStart' path='/Thread/Thread.tsx'/> |

### Timestamp

Custom UI component to display a date used in timestamps. It's used internally by the default `MessageTimestamp`, and to display a timestamp for edited messages.

| Type | Default |
| --------- | ----------------------------------------------------------------- |
| component | <GHComponentLink text='Timestamp' path='/Message/Timestamp.tsx'/> |

### TriggerProvider

Optional context provider that lets you override the default autocomplete triggers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ The following UI components are available for use:
- [`QuotedMessage`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/QuotedMessage.tsx) - shows a quoted
message UI wrapper when the sent message quotes a previous message

- [`Timestamp`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/Timestmap.tsx) - formats and displays a date,
used by `MessageTimestamp` and for edited message timestamps.

- [`MessageBouncePrompt`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageBounce/MessageBouncePrompt.tsx) -
presents options to deal with a message that got bounced by the moderation rules.

Expand Down Expand Up @@ -350,6 +353,8 @@ Theme string to be added to CSS class names.

## MessageTimestamp Props

This component has all of the same props as the underlying [`Timestamp`](#timestamp-props), except that instead of `timestamp` it uses `message.created_at` value from the `MessageContext`.

### calendar

If true, call the `Day.js` calendar function to get the date string to display.
Expand Down Expand Up @@ -418,7 +423,7 @@ The side of the message list to render MML components.
`QuotedMessage` only consumes context and does not accept any optional props.
:::

## MessageBouncePrompt
## MessageBouncePrompt props

This component is rendered in a modal dialog for messages that got bounced by the moderation rules.

Expand Down Expand Up @@ -460,3 +465,41 @@ The Message UI component will pass this callback to close the modal dialog `Mess
| Type |
| ----------------- |
| ReactEventHandler |

## Timestamp props

### calendar

If true, call the `Day.js` calendar function to get the date string to display.

| Type | Default |
| ------- | ------- |
| boolean | false |

### customClass

If provided, adds a CSS class name to the component's outer `time` container.

```jsx
<time className={customClass} />
```

| Type |
| ------ |
| string |

### format

If provided, overrides the default timestamp format.

| Type | Default |
| ------ | ------- |
| string | 'h:mmA' |

### timestamp

Either an ISO string with a date, or a Date object with a date to display.

| Type |
| -------------- |
| Date \| string |
7 changes: 4 additions & 3 deletions docusaurus/docs/React/guides/theming/message-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ The custom Message UI component built below imports and uses the following UI co
- [`SimpleReactionsList`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Reactions/SimpleReactionsList.tsx) - displays
a minimal list of the reactions added to a message (alternate option to [`ReactionsList`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Reactions/ReactionsList.tsx)).

- [`Timestamp`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/Timestmap.tsx) - formats and displays a date,
used by `MessageTimestamp` and for edited message timestamps.

### How it Fits Together

The sample code below assembles the above UI building blocks into a fully featured Message UI component. The UI components allow you to
Expand Down Expand Up @@ -105,9 +108,7 @@ export const CustomMessage = () => {
<MessageTimestamp />
</div>
</div>
{showDetailedReactions && canReact && (
<ReactionSelector ref={reactionSelectorRef} />
)}
{showDetailedReactions && canReact && <ReactionSelector ref={reactionSelectorRef} />}
<MessageText />
<MessageStatus />
{hasAttachments && <Attachment attachments={message.attachments} />}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ type ChannelPropsForwardedToComponentContext<
ThreadHeader?: ComponentContextValue<StreamChatGenerics>['ThreadHeader'];
/** Custom UI component to display the start of a threaded `MessageList`, defaults to and accepts same props as: [DefaultThreadStart](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Thread/Thread.tsx) */
ThreadStart?: ComponentContextValue<StreamChatGenerics>['ThreadStart'];
/** Custom UI component to display a date used in timestamps. It's used internally by the default `MessageTimestamp`, and to display a timestamp for edited messages. */
Timestamp?: ComponentContextValue<StreamChatGenerics>['Timestamp'];
/** Optional context provider that lets you override the default autocomplete triggers, defaults to: [DefaultTriggerProvider](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/DefaultTriggerProvider.tsx) */
TriggerProvider?: ComponentContextValue<StreamChatGenerics>['TriggerProvider'];
/** Custom UI component for the typing indicator, defaults to and accepts same props as: [TypingIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/TypingIndicator/TypingIndicator.tsx) */
Expand Down Expand Up @@ -1192,6 +1194,7 @@ const ChannelInner = <
ThreadHead: props.ThreadHead,
ThreadHeader: props.ThreadHeader,
ThreadStart: props.ThreadStart,
Timestamp: props.Timestamp,
TriggerProvider: props.TriggerProvider,
TypingIndicator: props.TypingIndicator,
UnreadMessagesNotification: props.UnreadMessagesNotification,
Expand Down
49 changes: 49 additions & 0 deletions src/components/Message/MessageEditedTimestamp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

import clsx from 'clsx';
import { useComponentContext, useMessageContext, useTranslationContext } from '../../context';
import { Timestamp as DefaultTimestamp } from './Timestamp';
import { isMessageEdited } from './utils';

import type { DefaultStreamChatGenerics } from '../../types';
import type { MessageTimestampProps } from './MessageTimestamp';

export type MessageEditedTimestampProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = MessageTimestampProps<StreamChatGenerics> & {
open: boolean;
};

export function MessageEditedTimestamp<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>({
message: propMessage,
open,
...timestampProps
}: MessageEditedTimestampProps<StreamChatGenerics>) {
const { t } = useTranslationContext('MessageEditedTimestamp');
const { message: contextMessage } = useMessageContext<StreamChatGenerics>(
'MessageEditedTimestamp',
);
const { Timestamp = DefaultTimestamp } = useComponentContext('MessageEditedTimestamp');
const message = propMessage || contextMessage;

if (!isMessageEdited(message)) {
return null;

Check warning on line 32 in src/components/Message/MessageEditedTimestamp.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Message/MessageEditedTimestamp.tsx#L32

Added line #L32 was not covered by tests
}

return (
<div
className={clsx(
'str-chat__message-edited-timestamp',
open
? 'str-chat__message-edited-timestamp--open'
: 'str-chat__message-edited-timestamp--collapsed',
)}
data-testid='message-edited-timestamp'
>
{t<string>('Edited')}{' '}
<Timestamp timestamp={message.message_text_updated_at} {...timestampProps} />
</div>
);
}
12 changes: 12 additions & 0 deletions src/components/Message/MessageSimple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp'
import {
areMessageUIPropsEqual,
isMessageBounced,
isMessageEdited,
messageHasAttachments,
messageHasReactions,
} from './utils';
Expand All @@ -34,6 +35,8 @@ import { MessageContextValue, useMessageContext } from '../../context/MessageCon
import type { MessageUIComponentProps } from './types';

import type { DefaultStreamChatGenerics } from '../../types/types';
import { useTranslationContext } from '../../context';
import { MessageEditedTimestamp } from './MessageEditedTimestamp';

type MessageSimpleWithContextProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
Expand Down Expand Up @@ -66,7 +69,9 @@ const MessageSimpleWithContext = <
threadList,
} = props;

const { t } = useTranslationContext('MessageSimple');
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false);

const {
Attachment,
Expand Down Expand Up @@ -106,13 +111,16 @@ const MessageSimpleWithContext = <
const showReplyCountButton = !threadList && !!message.reply_count;
const allowRetry = message.status === 'failed' && message.errorStatusCode !== 403;
const isBounced = isMessageBounced(message);
const isEdited = isMessageEdited(message);

let handleClick: (() => void) | undefined = undefined;

if (allowRetry) {
handleClick = () => handleRetry(message);
} else if (isBounced) {
handleClick = () => setIsBounceDialogOpen(true);
} else if (isEdited) {
handleClick = () => setEditedTimestampOpen((prev) => !prev);
}

const rootClassName = clsx(
Expand Down Expand Up @@ -228,6 +236,10 @@ const MessageSimpleWithContext = <
</span>
)}
<MessageTimestamp calendar customClass='str-chat__message-simple-timestamp' />
{isEdited && (
<span className='str-chat__mesage-simple-edited'>{t<string>('Edited')}</span>
)}
{isEdited && <MessageEditedTimestamp calendar open={isEditedTimestampOpen} />}
</div>
)}
</div>
Expand Down
45 changes: 9 additions & 36 deletions src/components/Message/MessageTimestamp.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React, { useMemo } from 'react';

import { useMessageContext } from '../../context/MessageContext';
import { isDate, useTranslationContext } from '../../context/TranslationContext';
import React from 'react';

import type { StreamMessage } from '../../context/ChannelStateContext';

import type { DefaultStreamChatGenerics } from '../../types/types';
import { getDateString } from '../../i18n/utils';

import { useMessageContext } from '../../context/MessageContext';
import { Timestamp as DefaultTimestamp } from './Timestamp';
import { useComponentContext } from '../../context';

export const defaultTimestampFormat = 'h:mmA';

Expand All @@ -28,37 +27,11 @@ const UnMemoizedMessageTimestamp = <
>(
props: MessageTimestampProps<StreamChatGenerics>,
) => {
const {
calendar = false,
customClass = '',
format = defaultTimestampFormat,
message: propMessage,
} = props;

const { formatDate, message: contextMessage } = useMessageContext<StreamChatGenerics>(
'MessageTimestamp',
);
const { tDateTimeParser } = useTranslationContext('MessageTimestamp');

const { message: propMessage, ...timestampProps } = props;
const { message: contextMessage } = useMessageContext<StreamChatGenerics>('MessageTimestamp');
const { Timestamp = DefaultTimestamp } = useComponentContext('MessageTimestamp');
const message = propMessage || contextMessage;

const messageCreatedAt =
message.created_at && isDate(message.created_at)
? message.created_at.toISOString()
: message.created_at;

const when = useMemo(
() => getDateString({ calendar, format, formatDate, messageCreatedAt, tDateTimeParser }),
[formatDate, calendar, tDateTimeParser, format, messageCreatedAt],
);

if (!when) return null;

return (
<time className={customClass} dateTime={messageCreatedAt} title={messageCreatedAt}>
{when}
</time>
);
return <Timestamp timestamp={message.created_at} {...timestampProps} />;
};

export const MessageTimestamp = React.memo(
Expand Down
Loading
Loading