Skip to content
Extraits de code Groupes Projets
Valider f32180b4 rédigé par DavePk04's avatar DavePk04
Parcourir les fichiers

feat: add reactions support to messages and improve UI

parent aa143724
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -17,6 +17,7 @@ export default class Message { ...@@ -17,6 +17,7 @@ export default class Message {
private _versions = writable([] as { content: string; date: Date }[]); private _versions = writable([] as { content: string; date: Date }[]);
private _feedbacks = writable([] as Feedback[]); private _feedbacks = writable([] as Feedback[]);
private _replyTo: string; private _replyTo: string;
private _reactions = writable<{ userId: string; emoji: string }[]>([]);
public constructor( public constructor(
id: number, id: number,
...@@ -89,6 +90,26 @@ export default class Message { ...@@ -89,6 +90,26 @@ export default class Message {
) as Message | undefined; ) as Message | undefined;
} }
get reactions(): Writable<{ userId: string; emoji: string }[]> {
return this._reactions;
}
addReaction(userId: string, emoji: string) {
this._reactions.update(reactions => {
const existing = reactions.find(r => r.userId === userId);
if (existing) {
existing.emoji = emoji;
} else {
reactions.push({ userId, emoji });
}
return reactions;
});
}
removeReaction(userId: string) {
this._reactions.update(reactions => reactions.filter(r => r.userId !== userId));
}
async update(content: string, metadata: { message: string; date: number }[]): Promise<boolean> { async update(content: string, metadata: { message: string; date: number }[]): Promise<boolean> {
const response = await updateMessageAPI( const response = await updateMessageAPI(
fetch, fetch,
...@@ -109,20 +130,18 @@ export default class Message { ...@@ -109,20 +130,18 @@ export default class Message {
async getMessageById(id: number): Promise<Message | null> { async getMessageById(id: number): Promise<Message | null> {
try { try {
const response = await getMessagesAPI(fetch, this._session.id); // Fetch all messages for the session const response = await getMessagesAPI(fetch, this._session.id);
if (!response) { if (!response) {
toastAlert('Failed to retrieve messages from the server.'); toastAlert('Failed to retrieve messages from the server.');
return null; return null;
} }
// Locate the message by ID in the response
const messageData = response.find((msg: any) => msg.id === id); const messageData = response.find((msg: any) => msg.id === id);
if (!messageData) { if (!messageData) {
toastAlert(`Message with ID ${id} not found.`); toastAlert(`Message with ID ${id} not found.`);
return null; return null;
} }
// Parse the message object
const parsedMessage = Message.parse(messageData, this._user, this._session); const parsedMessage = Message.parse(messageData, this._user, this._session);
if (!parsedMessage) { if (!parsedMessage) {
toastAlert(`Failed to parse message with ID ${id}`); toastAlert(`Failed to parse message with ID ${id}`);
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
import Message from '$lib/types/message'; import Message from '$lib/types/message';
import type User from '$lib/types/user'; import type User from '$lib/types/user';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { writable } from 'svelte/store';
let { let {
user, user,
...@@ -34,6 +35,9 @@ ...@@ -34,6 +35,9 @@
let historyModal: HTMLDialogElement; let historyModal: HTMLDialogElement;
let messageVersions = $state(message.versions); let messageVersions = $state(message.versions);
let showReactions = writable(false);
const emojiList = ["👍", "❤️", "😂", "😮", "😢", "👎"];
function startEdit() { function startEdit() {
isEdit = true; isEdit = true;
setTimeout(() => { setTimeout(() => {
...@@ -185,6 +189,20 @@ ...@@ -185,6 +189,20 @@
await message.deleteFeedback(feedback); await message.deleteFeedback(feedback);
} }
function reactToMessage(emoji: string) {
let reactions = get(message.reactions);
let currentReaction = reactions.find(r => r.userId === String(user.id));
if (currentReaction && currentReaction.emoji === emoji) {
message.removeReaction(String(user.id));
} else {
message.addReaction(String(user.id), emoji);
}
showReactions.set(false);
}
</script> </script>
<div <div
...@@ -201,7 +219,16 @@ ...@@ -201,7 +219,16 @@
/> />
</div> </div>
<div class="chat-bubble text-black" class:bg-blue-50={isSender} class:bg-gray-300={!isSender}> <div class="relative group chat-bubble text-black"
class:bg-blue-50={isSender}
class:bg-gray-300={!isSender}
onmouseover={() => showReactions.set(true)}
onmouseleave={() => showReactions.set(false)}
onfocus={() => showReactions.set(true)}
onblur={() => showReactions.set(false)}
role="button"
tabindex="0"
>
{#if replyToMessage} {#if replyToMessage}
<a <a
href={`#${replyToMessage.uuid}`} href={`#${replyToMessage.uuid}`}
...@@ -290,6 +317,29 @@ ...@@ -290,6 +317,29 @@
<Icon src={ArrowUturnLeft} class="w-5 h-full text-gray-500 hover:text-gray-800" /> <Icon src={ArrowUturnLeft} class="w-5 h-full text-gray-500 hover:text-gray-800" />
</button> </button>
{/if} {/if}
{#if get(message.reactions).length > 0}
<div class="flex items-center space-x-2 mt-2">
{#each get(message.reactions) as reaction}
<span class="text-lg cursor-pointer" title={`Reacted by ${reaction.userId}`}>
{reaction.emoji}
</span>
{/each}
</div>
{/if}
{#if $showReactions}
<div class="absolute bottom-0 left-0 flex space-x-2 p-2 bg-white border rounded-lg shadow-lg">
{#each emojiList as emoji}
<button type="button" class="cursor-pointer text-lg hover:bg-gray-300 p-1 rounded-lg"
onclick={() => reactToMessage(emoji)}
onkeydown={(e) => e.key === 'Enter' && reactToMessage(emoji)}
aria-label={`React with ${emoji}`}>
{emoji}
</button>
{/each}
</div>
{/if}
</div> </div>
<div class="chat-footer opacity-50"> <div class="chat-footer opacity-50">
<Icon src={Check} class="w-4 inline" /> <Icon src={Check} class="w-4 inline" />
...@@ -300,6 +350,7 @@ ...@@ -300,6 +350,7 @@
</button> </button>
{/if} {/if}
</div> </div>
</div> </div>
<div <div
class="absolute invisible rounded-xl border border-gray-400 bg-white divide-x" class="absolute invisible rounded-xl border border-gray-400 bg-white divide-x"
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter