waffle/frontend/src/components/MessageInput.svelte

174 lines
4.9 KiB
Svelte
Raw Normal View History

<script>
import { SendIcon } from "svelte-feather-icons";
2022-04-28 18:48:44 +03:00
import request from "../request";
2022-04-26 03:09:16 +03:00
import { apiRoute } from "../storage";
2022-08-07 03:00:14 +03:00
import { messagesStoreProvider, overlayStore, smallViewport, typingStore, userInfoStore } from "../stores";
export let channel;
let messageInput = "";
2022-08-03 18:55:12 +03:00
let messageTextarea;
2022-08-07 03:00:14 +03:00
let typingList = "?no one?";
let typingMessage = "is typing...";
$: messages = messagesStoreProvider.getStore(channel.id);
2022-08-07 03:00:14 +03:00
$: {
2022-08-07 21:57:41 +03:00
const typing = $typingStore.filter(a => a.channelId === channel.id);
const ownIndex = typing.findIndex(a => a.user.id === $userInfoStore.id);
2022-08-07 03:00:14 +03:00
if (ownIndex !== -1) {
typing.splice(ownIndex, 1);
}
if (typing.length === 0) {
typingList = "?no one?";
typingMessage = "is typing...";
} else if (typing.length === 1) {
2022-08-07 21:57:41 +03:00
typingList = `${typing[0].user.username}`;
2022-08-07 03:00:14 +03:00
typingMessage = "is typing...";
} else if (typing.length > 1) {
typingList = "";
for (let i = 0; i < typing.length; i++) {
const item = typing[i];
if (i == (typing.length - 1)) {
// we are at the end
2022-08-07 21:57:41 +03:00
typingList += `and ${item.user.username} `;
2022-08-07 03:00:14 +03:00
} else {
2022-08-07 21:57:41 +03:00
typingList += `${item.user.username}, `;
2022-08-07 03:00:14 +03:00
}
}
typingMessage = "are typing...";
}
}
const sendMessage = async () => {
2022-08-03 18:55:12 +03:00
messageTextarea.focus();
if (messageInput.trim() === "" || !$userInfoStore)
return;
// optimistically add message to store
const optimisticMessageId = Math.floor(Math.random() * 999999);
const optimisticMessage = {
id: optimisticMessageId,
content: messageInput,
channel_id: channel.id,
author_id: $userInfoStore.id,
author_username: $userInfoStore.username,
created_at: Date.now().toString(),
_isPending: true
};
messages.addMessage(optimisticMessage);
messageInput = "";
const res = await request("POST", apiRoute(`channels/${channel.id}/messages`), true, {
content: optimisticMessage.content,
optimistic_id: optimisticMessageId
});
if (res.success && res.ok) {
messages.setMessage(optimisticMessageId, res.json);
} else {
messages.deleteMessage({
id: optimisticMessageId
});
overlayStore.open("toast", {
message: "Couldn't send message"
});
}
};
const onKeydown = async (e) => {
2022-05-07 16:50:04 +03:00
if (e.code === "Enter") {
if (e.shiftKey) {
return;
} else {
e.preventDefault();
await sendMessage();
}
}
};
2022-08-07 03:00:14 +03:00
const onInput = () => {
typingStore.didInputKey();
};
</script>
<style>
.message-input-container {
display: flex;
2022-08-07 03:00:14 +03:00
flex-direction: column;
justify-content: center;
width: 100%;
padding: var(--space-norm);
2022-08-07 03:00:14 +03:00
padding-bottom: 0;
}
.message-input {
width: 100%;
background-color : var(--background-color-2);
border: none;
color: currentColor;
border-radius: var(--radius-md);
padding: var(--space-sm);
font-size: inherit;
line-height: inherit;
2022-08-03 04:49:03 +03:00
resize: none;
}
.message-input::placeholder {
color: var(--foreground-color-3);
}
.send-button {
margin-left: var(--space-sm);
}
2022-08-07 03:00:14 +03:00
.invisible {
visibility: hidden;
}
.inner-input-container {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.typing-info-container {
padding-left: var(--space-xxs);
}
.typing-list {
font-weight: bolder;
}
.typing-message {
color: var(--foreground-color-2);
}
</style>
<div class="message-input-container">
2022-08-07 03:00:14 +03:00
<div class="inner-input-container">
<textarea
placeholder={$smallViewport ? `Message #${channel.name}` : `Send something interesting to #${channel.name}`}
type="text"
class="message-input"
rows="1"
on:keydown={ onKeydown }
on:input={ onInput }
bind:value={ messageInput }
bind:this={ messageTextarea }
/>
{#if $smallViewport}
<button class="icon-button send-button" on:click="{ sendMessage }">
<SendIcon />
</button>
{/if}
</div>
<div class="typing-info-container">
<span class="typing-list" class:invisible={ typingList === "?no one?" }>{ typingList }</span>
<span class="typing-message" class:invisible={ typingList === "?no one?" }>{ typingMessage }</span>
</div>
</div>