feat: reactions

This commit is contained in:
Timothy Jaeryang Baek
2024-12-30 23:06:34 -08:00
parent 4b0fa112bb
commit f93c2e4a8d
9 changed files with 479 additions and 69 deletions

View File

@@ -285,6 +285,77 @@ export const updateMessage = async (
return res;
};
export const addReaction = async (token: string = '', channel_id: string, message_id: string, name: string) => {
let error = null;
const res = await fetch(
`${WEBUI_API_BASE_URL}/channels/${channel_id}/messages/${message_id}/reactions/add`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({ name })
}
)
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
}
export const removeReaction = async (token: string = '', channel_id: string, message_id: string, name: string) => {
let error = null;
const res = await fetch(
`${WEBUI_API_BASE_URL}/channels/${channel_id}/messages/${message_id}/reactions/remove`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({ name })
}
)
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
}
export const deleteMessage = async (token: string = '', channel_id: string, message_id: string) => {
let error = null;

View File

@@ -1,13 +1,13 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { onDestroy, onMount, tick } from 'svelte';
import { goto } from '$app/navigation';
import { chatId, showSidebar, socket, user } from '$lib/stores';
import { getChannelById, getChannelMessages, sendMessage } from '$lib/apis/channels';
import Messages from './Messages.svelte';
import MessageInput from './MessageInput.svelte';
import { goto } from '$app/navigation';
import Navbar from './Navbar.svelte';
export let id = '';
@@ -84,6 +84,13 @@
} else if (type === 'message:delete') {
console.log('message:delete', data);
messages = messages.filter((message) => message.id !== data.id);
} else if (type === 'message:reaction') {
console.log('message:reaction', data);
const idx = messages.findIndex((message) => message.id === data.id);
if (idx !== -1) {
messages[idx] = data;
}
} else if (type === 'typing') {
if (event.user.id === $user.id) {
return;

View File

@@ -11,12 +11,12 @@
dayjs.extend(isYesterday);
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
import { settings } from '$lib/stores';
import { settings, user } from '$lib/stores';
import Message from './Messages/Message.svelte';
import Loader from '../common/Loader.svelte';
import Spinner from '../common/Spinner.svelte';
import { deleteMessage, updateMessage } from '$lib/apis/channels';
import { addReaction, deleteMessage, removeReaction, updateMessage } from '$lib/apis/channels';
const i18n = getContext('i18n');
@@ -109,6 +109,31 @@
return null;
});
}}
onReaction={(name) => {
if (
message.reactions
.find((reaction) => reaction.name === name)
?.user_ids?.includes($user.id) ??
false
) {
const res = removeReaction(
localStorage.token,
message.channel_id,
message.id,
name
).catch((error) => {
toast.error(error);
return null;
});
} else {
const res = addReaction(localStorage.token, message.channel_id, message.id, name).catch(
(error) => {
toast.error(error);
return null;
}
);
}
}}
/>
{/each}

View File

@@ -35,25 +35,14 @@
export let onDelete: Function = () => {};
export let onEdit: Function = () => {};
export let onReaction: Function = () => {};
let showButtons = false;
let edit = false;
let editedContent = null;
let showDeleteConfirmDialog = false;
let reactions = [
{
name: 'red_circle',
user_ids: ['U07KUHZSYER'],
count: 1
},
{
name: '+1',
user_ids: [$user.id],
count: 1
}
];
const formatDate = (inputDate) => {
const date = dayjs(inputDate);
const now = dayjs();
@@ -92,7 +81,13 @@
<div
class="flex gap-1 rounded-lg bg-white dark:bg-gray-850 shadow-md p-0.5 border border-gray-100 dark:border-gray-800"
>
<ReactionPicker onClose={() => (showButtons = false)}>
<ReactionPicker
onClose={() => (showButtons = false)}
onSubmit={(name) => {
showButtons = false;
onReaction(name);
}}
>
<Tooltip content={$i18n.t('Add Reaction')}>
<button
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
@@ -271,38 +266,49 @@
>{/if}
</div>
{#if reactions.length > 0}
{#if message.reactions.length > 0}
<div>
<div class="flex items-center gap-1 mt-1 mb-2">
{#each reactions as reaction}
<button
class="flex items-center gap-1.5 transition rounded-xl px-2 py-1 cursor-pointer {reaction.user_ids.includes(
$user.id
)
? ' bg-blue-500/10 outline outline-blue-500/50 outline-1'
: 'bg-gray-500/10 hover:outline hover:outline-gray-700/30 dark:hover:outline-gray-300/30 hover:outline-1'}"
>
{#if $shortCodesToEmojis[reaction.name]}
<img
src="/assets/emojis/{$shortCodesToEmojis[reaction.name].toLowerCase()}.svg"
alt={reaction.name}
class=" size-4"
/>
{:else}
<div>
{reaction.name}
</div>
{/if}
{#each message.reactions as reaction}
<Tooltip content={`:${reaction.name}:`}>
<button
class="flex items-center gap-1.5 transition rounded-xl px-2 py-1 cursor-pointer {reaction.user_ids.includes(
$user.id
)
? ' bg-blue-500/10 outline outline-blue-500/50 outline-1'
: 'bg-gray-500/10 hover:outline hover:outline-gray-700/30 dark:hover:outline-gray-300/30 hover:outline-1'}"
on:click={() => {
onReaction(reaction.name);
}}
>
{#if $shortCodesToEmojis[reaction.name]}
<img
src="/assets/emojis/{$shortCodesToEmojis[
reaction.name
].toLowerCase()}.svg"
alt={reaction.name}
class=" size-4"
/>
{:else}
<div>
{reaction.name}
</div>
{/if}
{#if reaction.user_ids.length > 0}
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
{reaction.user_ids?.length}
</div>
{/if}
</button>
{#if reaction.user_ids.length > 0}
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
{reaction.user_ids?.length}
</div>
{/if}
</button>
</Tooltip>
{/each}
<ReactionPicker>
<ReactionPicker
onSubmit={(name) => {
onReaction(name);
}}
>
<Tooltip content={$i18n.t('Add Reaction')}>
<div
class="flex items-center gap-1.5 bg-gray-500/10 hover:outline hover:outline-gray-700/30 dark:hover:outline-gray-300/30 hover:outline-1 transition rounded-xl px-1 py-1 cursor-pointer text-gray-500 dark:text-gray-400"

View File

@@ -7,6 +7,7 @@
import Tooltip from '$lib/components/common/Tooltip.svelte';
export let onClose = () => {};
export let onSubmit = (name) => {};
export let side = 'top';
export let align = 'start';
@@ -95,8 +96,15 @@
.join(', ')}
placement="top"
>
<div
<button
class="p-1.5 rounded-lg cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition"
on:click={() => {
typeof emojiShortCodes[emoji] === 'string'
? onSubmit(emojiShortCodes[emoji])
: onSubmit(emojiShortCodes[emoji][0]);
show = false;
}}
>
<img
src="/assets/emojis/{emoji.toLowerCase()}.svg"
@@ -104,7 +112,7 @@
class="size-5"
loading="lazy"
/>
</div>
</button>
</Tooltip>
{/each}
</div>