Skip to content

Commit

Permalink
Add message deletion functionality to chat API and React hooks
Browse files Browse the repository at this point in the history
- This commit introduces the ability to `soft` delete messages in the chat API with `DeleteMessageParams`.
It exposes this through the Messages feature as a new `delete` method.
- Updated related testing and documentation.
- Some minor improvements to messages.test.ts for clarity.

- The useMessage hook now also supports this functionality via a `deleteMessage` method returned from the hook.
- Updated related testing and documentation.

- The demo app has been updated to show the new deletion mechanic.
It uses the `react-icons` to display a bin icon, clicking this will delete the highlighted message.
  • Loading branch information
splindsay-92 committed Nov 6, 2024
1 parent 4285d01 commit aabca88
Show file tree
Hide file tree
Showing 17 changed files with 3,552 additions and 1,435 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,23 @@ const message = await room.messages.send({
});
```

### Deleting messages

To delete a message, call `delete` on the `room.messages` property, with the original message you want to delete.

You can supply optional parameters to the `delete` method to provide additional context for the deletion.

These additional parameters are:
* `description`: a string that can be used to inform others as to why the message was deleted.
* `metadata`: a map of extra information that can be attached to the deletion message.

The return of this call will be the deleted message, as it would appear to other subscribers of the room.
This is a _soft delete_ and the message will still be available in the history, but with the `deletedAt` property set.

```ts
const deletedMessage = await room.messages.delete(message, { description: 'This message was deleted for ...' });
```

### Subscribing to incoming messages

To subscribe to incoming messages, call `subscribe` with your listener.
Expand Down
11 changes: 10 additions & 1 deletion demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"clsx": "^2.1.1",
"nanoid": "^5.0.7",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-icons": "^5.3.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
Expand Down
33 changes: 30 additions & 3 deletions demo/src/components/MessageComponent/MessageComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Message } from '@ably/chat';
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import clsx from 'clsx';
import { FaTrash } from 'react-icons/fa6';

function twoDigits(input: number): string {
if (input === 0) {
Expand All @@ -18,13 +19,23 @@ interface MessageProps {
message: Message;

onMessageClick?(id: string): void;

onMessageDelete?(msg: Message): void;
}

export const MessageComponent: React.FC<MessageProps> = ({ id, self = false, message, onMessageClick }) => {
export const MessageComponent: React.FC<MessageProps> = ({
id,
self = false,
message,
onMessageClick,
onMessageDelete,
}) => {
const handleMessageClick = useCallback(() => {
onMessageClick?.(id);
}, [id, onMessageClick]);

const [hovered, setHovered] = useState(false);

let displayCreatedAt: string;
if (Date.now() - message.createdAt.getTime() < 1000 * 60 * 60 * 24) {
// last 24h show the time
Expand All @@ -43,14 +54,21 @@ export const MessageComponent: React.FC<MessageProps> = ({ id, self = false, mes
twoDigits(message.createdAt.getMinutes());
}

const handleDelete = useCallback(() => {
// Add your delete handling logic here
onMessageDelete?.(message);
}, [message, onMessageDelete]);

return (
<div
className="chat-message"
onClick={handleMessageClick}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<div className={clsx('flex items-end', { ['justify-end']: self, ['justify-start']: !self })}>
<div
className={clsx('flex flex-col text max-w-xs mx-2', {
className={clsx('flex flex-col text max-w-xs mx-2 relative', {
['items-end order-1']: self,
['items-start order-2']: !self,
})}
Expand All @@ -69,6 +87,15 @@ export const MessageComponent: React.FC<MessageProps> = ({ id, self = false, mes
})}
>
{message.text}
{hovered && (
<FaTrash
className="ml-2 cursor-pointer text-red-500"
onClick={(e) => {
e.stopPropagation();
handleDelete();
}}
/>
)}
</div>
</div>
</div>
Expand Down
49 changes: 45 additions & 4 deletions demo/src/containers/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { MessageInput } from '../../components/MessageInput';
import { useChatClient, useChatConnection, useMessages, useRoomReactions, useTyping } from '@ably/chat/react';
import { ReactionInput } from '../../components/ReactionInput';
import { ConnectionStatusComponent } from '../../components/ConnectionStatusComponent/ConnectionStatusComponent.tsx';
import { ConnectionLifecycle, Message, MessageEventPayload, PaginatedResult, Reaction } from '@ably/chat';
import {
ConnectionLifecycle,
Message,
MessageEvents,
MessageEventPayload,
PaginatedResult,
Reaction,
} from '@ably/chat';

export const Chat = () => {
const chatClient = useChatClient();
Expand All @@ -15,9 +22,26 @@ export const Chat = () => {

const isConnected: boolean = currentStatus === ConnectionLifecycle.Connected;

const { send: sendMessage, getPreviousMessages } = useMessages({
const {
send: sendMessage,
getPreviousMessages,
deleteMessage,
} = useMessages({
listener: (message: MessageEventPayload) => {
setMessages((prevMessage) => [...prevMessage, message.message]);
switch (message.type) {
case MessageEvents.Created:
setMessages((prevMessage) => [...prevMessage, message.message]);
break;
case MessageEvents.Deleted:
setMessages((prevMessage) => {
return prevMessage.filter((m) => {
return m.timeserial !== message.message.timeserial;
});
});
break;
default:
console.error('Unknown message', message);
}
},
onDiscontinuity: (discontinuity) => {
console.log('Discontinuity', discontinuity);
Expand Down Expand Up @@ -47,7 +71,9 @@ export const Chat = () => {
if (getPreviousMessages && loading) {
getPreviousMessages({ limit: 50 })
.then((result: PaginatedResult<Message>) => {
setMessages(result.items.reverse());
// reverse the messages so they are displayed in the correct order
// and don't include deleted messages
setMessages(result.items.filter((m) => !m.isDeleted).reverse());
setLoading(false);
})
.catch((error: unknown) => {
Expand Down Expand Up @@ -144,6 +170,21 @@ export const Chat = () => {
key={msg.timeserial}
self={msg.clientId === clientId}
message={msg}
onMessageClick={(id) => {
console.log(
'Message clicked',
messages.find((m) => m.timeserial === id),
);
}}
onMessageDelete={(msg) => {
deleteMessage(msg, { description: 'deleted by user' }).then((deletedMessage: Message) => {
setMessages((prevMessages) => {
return prevMessages.filter((m) => {
return m.timeserial !== deletedMessage.timeserial;
});
});
});
}}
></MessageComponent>
))}
<div ref={messagesEndRef} />
Expand Down
Loading

0 comments on commit aabca88

Please sign in to comment.