2022-04-23 01:06:04 +03:00
|
|
|
<script>
|
2022-08-31 12:07:25 +03:00
|
|
|
import { onDestroy, onMount } from "svelte";
|
2022-04-28 18:48:44 +03:00
|
|
|
import request from "../request";
|
2022-08-31 11:46:51 +03:00
|
|
|
import { apiRoute, getItem } from "../storage";
|
2022-09-01 19:56:19 +03:00
|
|
|
import { messagesStoreProvider, overlayStore, selectedChannel, setMessageInputEvent, smallViewport, typingStore, userInfoStore } from "../stores";
|
2022-04-23 01:06:04 +03:00
|
|
|
|
|
|
|
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...";
|
2022-04-23 01:06:04 +03:00
|
|
|
|
|
|
|
$: 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...";
|
|
|
|
}
|
|
|
|
}
|
2022-04-23 01:06:04 +03:00
|
|
|
|
2022-04-27 20:12:04 +03:00
|
|
|
const sendMessage = async () => {
|
2022-08-03 18:55:12 +03:00
|
|
|
messageTextarea.focus();
|
|
|
|
|
2022-04-23 01:06:04 +03:00
|
|
|
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, {
|
2022-08-20 00:12:27 +03:00
|
|
|
content: optimisticMessage.content,
|
|
|
|
optimistic_id: optimisticMessageId
|
2022-04-23 01:06:04 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
if (res.success && res.ok) {
|
|
|
|
messages.setMessage(optimisticMessageId, res.json);
|
|
|
|
} else {
|
|
|
|
messages.deleteMessage({
|
|
|
|
id: optimisticMessageId
|
|
|
|
});
|
2022-09-03 17:30:39 +03:00
|
|
|
overlayStore.toast("Couldn't send message");
|
2022-04-23 01:06:04 +03:00
|
|
|
}
|
|
|
|
};
|
2022-04-27 20:12:04 +03:00
|
|
|
|
|
|
|
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-04-27 20:12:04 +03:00
|
|
|
};
|
2022-08-07 03:00:14 +03:00
|
|
|
|
|
|
|
const onInput = () => {
|
|
|
|
typingStore.didInputKey();
|
|
|
|
};
|
2022-08-31 12:07:25 +03:00
|
|
|
|
2022-09-01 19:56:19 +03:00
|
|
|
|
|
|
|
const unsubscribers = [];
|
|
|
|
|
|
|
|
|
2022-08-31 12:07:25 +03:00
|
|
|
// Focus the text area when the component first loads, or when the user selects another channel
|
2022-09-02 15:08:47 +03:00
|
|
|
const focusTextarea = () => {
|
|
|
|
if (messageTextarea && getItem("ui:useragent:formFactor") !== "touch") {
|
|
|
|
messageTextarea.focus();
|
|
|
|
}
|
|
|
|
};
|
2022-08-31 12:07:25 +03:00
|
|
|
onMount(focusTextarea);
|
2022-09-02 15:08:47 +03:00
|
|
|
unsubscribers.push(selectedChannel.watch(focusTextarea));
|
2022-09-01 19:56:19 +03:00
|
|
|
|
|
|
|
// Handle the setMessageInput event
|
2022-09-02 12:35:33 +03:00
|
|
|
unsubscribers.push(setMessageInputEvent.watch((value) => {
|
2022-09-01 19:56:19 +03:00
|
|
|
messageInput = value;
|
2022-09-02 15:08:47 +03:00
|
|
|
if (messageTextarea) {
|
|
|
|
messageTextarea.focus();
|
|
|
|
}
|
2022-09-01 19:56:19 +03:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
unsubscribers.forEach(e => e());
|
|
|
|
});
|
2022-04-23 01:06:04 +03:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
.message-input-container {
|
2022-04-27 20:12:04 +03:00
|
|
|
display: flex;
|
2022-08-07 03:00:14 +03:00
|
|
|
flex-direction: column;
|
2022-04-27 20:12:04 +03:00
|
|
|
justify-content: center;
|
2022-04-23 01:06:04 +03:00
|
|
|
width: 100%;
|
|
|
|
padding: var(--space-norm);
|
2022-08-07 03:00:14 +03:00
|
|
|
padding-bottom: 0;
|
2022-04-23 01:06:04 +03:00
|
|
|
}
|
2022-08-20 03:32:31 +03:00
|
|
|
|
|
|
|
.message-input-container.small {
|
|
|
|
padding-top: var(--space-sm);
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-input.small {
|
|
|
|
padding: var(--space-xsplus);
|
|
|
|
padding-left: var(--space-sm);
|
|
|
|
border-radius: 1.4em;
|
|
|
|
}
|
2022-04-23 01:06:04 +03:00
|
|
|
|
|
|
|
.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;
|
2022-04-23 01:06:04 +03:00
|
|
|
}
|
|
|
|
|
2022-09-01 21:07:53 +03:00
|
|
|
.message-input:focus-visible {
|
|
|
|
outline: 2px solid var(--purple-2);
|
|
|
|
}
|
|
|
|
|
2022-04-23 01:06:04 +03:00
|
|
|
.message-input::placeholder {
|
2022-08-28 17:57:01 +03:00
|
|
|
color: var(--foreground-color-4);
|
2022-04-23 01:06:04 +03:00
|
|
|
}
|
2022-04-27 20:12:04 +03:00
|
|
|
|
|
|
|
.send-button {
|
|
|
|
margin-left: var(--space-sm);
|
2022-08-20 03:32:31 +03:00
|
|
|
border-radius: 50%;
|
|
|
|
background-color: var(--purple-1);
|
2022-08-31 12:58:12 +03:00
|
|
|
padding: 12px;
|
|
|
|
aspect-ratio: 1;
|
2022-08-20 03:32:31 +03:00
|
|
|
flex-shrink: 0;
|
2022-04-27 20:12:04 +03:00
|
|
|
}
|
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);
|
|
|
|
}
|
2022-04-23 01:06:04 +03:00
|
|
|
</style>
|
|
|
|
|
2022-08-20 03:32:31 +03:00
|
|
|
<div class="message-input-container" class:small={ $smallViewport }>
|
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 }
|
2022-08-31 11:46:51 +03:00
|
|
|
class:small={ $smallViewport || getItem("ui:alwaysUseMobileChatBar") }
|
2022-08-07 03:00:14 +03:00
|
|
|
/>
|
2022-08-31 11:46:51 +03:00
|
|
|
{#if $smallViewport || getItem("ui:alwaysUseMobileChatBar")}
|
2022-09-18 01:41:59 +03:00
|
|
|
<button class="icon-button send-button material-icons-outlined" on:click="{ sendMessage }">
|
|
|
|
arrow_upward
|
2022-08-07 03:00:14 +03:00
|
|
|
</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>
|
2022-04-23 01:06:04 +03:00
|
|
|
</div>
|