a bunch of bug fixes and improvements
This commit is contained in:
@@ -2,9 +2,6 @@
|
||||
import emojiJson from '~/assets/json/emoji.json';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
opened: Boolean,
|
||||
},
|
||||
emits: ['picked-emoji'],
|
||||
data() {
|
||||
return {
|
||||
@@ -22,7 +19,9 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
emojiStyles(emojiShortName: string, width: number) {
|
||||
emojiStyles(emojiShortName: string | undefined, width: number) {
|
||||
if (!emojiShortName) return;
|
||||
|
||||
const emojis = emojiJson;
|
||||
const emoji = emojis.find((e) => e.short_names[0] === emojiShortName);
|
||||
if (!emoji) return;
|
||||
@@ -38,17 +37,17 @@ export default {
|
||||
};
|
||||
},
|
||||
scrollTo(categoryName: string) {
|
||||
const emojiPane = document.getElementById('emojiPane');
|
||||
const emojiPane = (this.$refs.emojiPane as HTMLDivElement);
|
||||
const category = document.getElementById(categoryName);
|
||||
if (!emojiPane || !category) return;
|
||||
emojiPane.scrollTop = category.offsetTop - 96;
|
||||
emojiPane.scrollTop = category.offsetTop - 550;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="p-3">
|
||||
<div class="py-1.5 flex flex-col">
|
||||
<div class="flex-row gap-x-2 overflow-x-scroll">
|
||||
<button
|
||||
@@ -232,13 +231,13 @@ export default {
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="emoji-pane"
|
||||
class="overflow-hidden overflow-y-scroll scroll-smooth EmojiPicker max-h-[450px]"
|
||||
ref="emojiPane"
|
||||
class="overflow-hidden overflow-y-scroll scroll-smooth EmojiPicker max-h-[calc(475px-24px-48px)]"
|
||||
>
|
||||
<div
|
||||
v-for="category in categories"
|
||||
:key="category.name"
|
||||
class="text-black flex flex-col category bg-[var(--primary-dark)]"
|
||||
class="text-black flex flex-col category bg-[var(--secondary-bg)]"
|
||||
>
|
||||
<h6 class="uppercase text-[var(--primary-text)] sticky top-0 bg-inherit z-10 py-1">
|
||||
{{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<span
|
||||
class="before:bg-[var(--primary-accent)] before:h-2 before:w-2 before:inline-block before:my-auto before:rounded-full before:mr-1"
|
||||
/>
|
||||
<span>{{ invite.server.participants.filter((e: IUser) => e.online === true).length }} Online</span>
|
||||
<span>{{ invite.server.participants.filter((e: SafeUser) => !!e.online).length }} Online</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full justify-end">
|
||||
@@ -40,7 +40,7 @@
|
||||
import { PropType } from 'vue';
|
||||
import { useServerStore } from '~/stores/serverStore';
|
||||
import { useUserStore } from '~/stores/userStore';
|
||||
import { IInviteCode, IUser } from '~/types';
|
||||
import { IInviteCode, IServer, IUser, SafeUser } from '~/types';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -57,16 +57,18 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
userInServer(): boolean {
|
||||
return !!this.invite.server.participants.find((e: IUser) => e.id === this.user?.id);
|
||||
return !!this.invite.server.participants.find((e: SafeUser) => e.id === this.user?.id);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async joinServer(invite: IInviteCode) {
|
||||
if (this.userInServer) return;
|
||||
|
||||
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
|
||||
const { server } = await $fetch('/api/guilds/joinGuild', { method: 'POST', body: { inviteId: invite.id }, headers });
|
||||
const { server } = await $fetch('/api/guilds/joinGuild', { method: 'POST', body: { inviteId: invite.id }, headers }) as { server: IServer };
|
||||
if (!server) return;
|
||||
this.servers?.push(server);
|
||||
|
||||
useServerStore().addServer(server);
|
||||
this.invite.server.participants.push(this.user);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -118,15 +118,40 @@
|
||||
>
|
||||
<div class="message-content">
|
||||
<div class="message-sender-text">
|
||||
<p
|
||||
v-if="showUsername"
|
||||
class="mb-1 font-semibold w-fit"
|
||||
<p
|
||||
v-if="showUsername"
|
||||
class="flex flex-row"
|
||||
>
|
||||
{{ message.creator.username }}
|
||||
<span
|
||||
ref="username"
|
||||
class="mb-1 font-semibold w-fit cursor-pointer hover:underline"
|
||||
@click="openUserProfile()"
|
||||
>
|
||||
{{ message.creator.username }}
|
||||
</span>
|
||||
<span
|
||||
v-if="userIsOwner"
|
||||
class="ml-0.5"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
class="text-yellow-300"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m12 6l4 6l5-4l-2 10H5L3 8l5 4z"
|
||||
/></svg>
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
class="break-words max-w-full"
|
||||
v-html="message.body"
|
||||
<div
|
||||
class="break-words max-w-full whitespace-pre-wrap"
|
||||
v-html="parseMessageBody(message.body, participants)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -206,6 +231,10 @@ export default {
|
||||
computed: {
|
||||
reactions(): IReaction[] {
|
||||
return this.message.reactions?.filter((e) => e.users.length > 0) || [];
|
||||
},
|
||||
userIsOwner(): boolean {
|
||||
if (useActiveStore().type !== 'server') return false;
|
||||
return !!useActiveStore().server.server.participants.find((e) => e.id === this.message.creator.id)?.roles?.find((e) => e.owner) || false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -214,7 +243,8 @@ export default {
|
||||
if (useEmojiPickerStore().emojiPickerData.openedBy?.messageId !== this.message.id) return;
|
||||
const replacementEmoji = emojiJson.find((e) => e.short_names[0] === emoji);
|
||||
if (!replacementEmoji?.emoji) return;
|
||||
if (this.message.reactions?.find((e) => e.emoji === replacementEmoji.emoji)) return;
|
||||
if (this.message.reactions?.find((e) => e.emoji === replacementEmoji.emoji) &&
|
||||
this.message.reactions?.find((e) => e.emoji === replacementEmoji.emoji)?.users.find((e) => e.id === this.user?.id)) return;
|
||||
this.toggleReaction(replacementEmoji.emoji);
|
||||
});
|
||||
},
|
||||
@@ -222,11 +252,15 @@ export default {
|
||||
async toggleReaction(emoji: string) {
|
||||
let { message } = await $fetch(`/api/channels/${this.channelId}/messages/${this.message.id}/reactions/${emoji}`, { method: 'POST' }) as { message: IMessage };
|
||||
|
||||
message.body = parseMessageBody(message.body, this.participants);
|
||||
|
||||
useActiveStore().updateMessage(message);
|
||||
},
|
||||
openEmojiPicker() {
|
||||
console.log(useEmojiPickerStore().emojiPickerData);
|
||||
if (useEmojiPickerStore().emojiPickerData.opened && useEmojiPickerStore().emojiPickerData.type === 'emojiPicker' && useEmojiPickerStore().emojiPickerData.openedBy?.messageId === this.message.id) {
|
||||
useEmojiPickerStore().closeEmojiPicker();
|
||||
return;
|
||||
}
|
||||
|
||||
const actionButtons = document.getElementById(`actions-${this.message.id}`);
|
||||
if (!actionButtons) return;
|
||||
|
||||
@@ -235,6 +269,7 @@ export default {
|
||||
if (top + 522 > window.innerHeight) top = window.innerHeight - 522;
|
||||
|
||||
const payload = {
|
||||
type: 'emojiPicker',
|
||||
top,
|
||||
right: actionButtons.clientWidth + 40,
|
||||
openedBy: {
|
||||
@@ -243,7 +278,35 @@ export default {
|
||||
}
|
||||
} as IPopupData;
|
||||
|
||||
useEmojiPickerStore().toggleEmojiPicker(payload);
|
||||
useEmojiPickerStore().openEmojiPicker(payload);
|
||||
},
|
||||
openUserProfile() {
|
||||
const messagePane = document.getElementById('messagePane') as HTMLDivElement;
|
||||
const usernameElement = this.$refs.username as HTMLParagraphElement;
|
||||
if (!usernameElement || !messagePane) return;
|
||||
|
||||
const elementRect = usernameElement.getBoundingClientRect();
|
||||
let top = elementRect.top + window.pageYOffset;
|
||||
const left = window.innerWidth - messagePane.clientWidth + 28 + usernameElement.clientWidth;
|
||||
if (top + 522 > window.innerHeight) top = window.innerHeight - 522;
|
||||
|
||||
if (useEmojiPickerStore().emojiPickerData.opened &&
|
||||
useEmojiPickerStore().emojiPickerData.type === 'userInfo' &&
|
||||
useEmojiPickerStore().emojiPickerData.userId === this.message.creator.id &&
|
||||
useEmojiPickerStore().emojiPickerData.top === top &&
|
||||
useEmojiPickerStore().emojiPickerData.left === left) {
|
||||
useEmojiPickerStore().closeEmojiPicker();
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
type: 'userInfo',
|
||||
top,
|
||||
left,
|
||||
userId: this.message.creator.id
|
||||
} as IPopupData;
|
||||
|
||||
useEmojiPickerStore().openEmojiPicker(payload);
|
||||
},
|
||||
emojiStyles(emoji: string, width: number) {
|
||||
const emojis = emojiJson;
|
||||
@@ -298,6 +361,7 @@ pre.codeblock code {
|
||||
}
|
||||
|
||||
code.inline-code {
|
||||
color: var(--primary-accent);
|
||||
background-color: var(--secondary-bg);
|
||||
padding: 0.2rem;
|
||||
font-size: 85%;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
id="messagePane"
|
||||
class="h-full relative bg-[var(--primary-bg)] flex flex-col"
|
||||
@mouseenter="mouseEnter"
|
||||
@mouseleave="mouseLeave"
|
||||
@@ -77,8 +78,8 @@
|
||||
:key="message.id"
|
||||
:message="message"
|
||||
:shift-pressed="shiftPressed"
|
||||
:show-username="i === 0 || channel.messages[i - 1]?.creator.id !== message.creator.id"
|
||||
:classes="calculateMessageClasses(message, i)"
|
||||
:show-username="calculateMessageDesign(message, i).showUsername"
|
||||
:classes="calculateMessageDesign(message, i).classes"
|
||||
:channel-id="channel.id"
|
||||
:participants="participants"
|
||||
/>
|
||||
@@ -214,21 +215,21 @@ export default {
|
||||
methods: {
|
||||
async sendMessage() {
|
||||
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
|
||||
if (!this.messageContent) return;
|
||||
if (!this.messageContent || !this.messageContent.trim()) return;
|
||||
|
||||
let message: IMessage = await $fetch(`/api/channels/${this.channel.id}/sendMessage`, { method: 'post', body: { body: this.messageContent }, headers });
|
||||
|
||||
if (!message) return;
|
||||
if (this.channel.messages.includes(message)) return;
|
||||
|
||||
message.body = parseMessageBody(message.body, this.participants);
|
||||
|
||||
this.channel.messages.push(message);
|
||||
useActiveStore().addMessage(message);
|
||||
this.messageContent = '';
|
||||
const conversationDiv = this.$refs.conversationPane as HTMLDivElement;
|
||||
if (!conversationDiv) throw new Error('wtf');
|
||||
|
||||
this.scrollToBottom();
|
||||
setTimeout(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
},
|
||||
scrollToBottom() {
|
||||
const conversationDiv = this.$refs.conversationPane as HTMLDivElement;
|
||||
@@ -273,20 +274,20 @@ export default {
|
||||
this.shiftPressed = false;
|
||||
}
|
||||
},
|
||||
calculateMessageClasses(message: IMessage, i: number) {
|
||||
if (i === 0 || this.channel.messages[i - 1]?.creator.id !== message.creator.id) {
|
||||
if (i !== this.channel.messages.length - 1 || this.channel.messages[i + 1]?.creator.id === message.creator.id) {
|
||||
return 'mb-0 pb-0.5';
|
||||
calculateMessageDesign(message: IMessage, i: number) {
|
||||
if (i === 0 || (this.channel.messages[i - 1]?.creator.id !== message.creator.id || new Date(this.channel.messages[i-1]?.createdAt).getTime()+((30*60)*1000)<new Date(this.channel.messages[i]?.createdAt).getTime())) {
|
||||
if (i !== this.channel.messages.length - 1) {
|
||||
return { classes: 'mb-0 pb-0.5', showUsername: true };
|
||||
}
|
||||
} else {
|
||||
if (i !== this.channel.messages.length - 1 || this.channel.messages[i + 1]?.creator.id === message.creator.id) {
|
||||
return 'mt-0 mb-0 !py-0.5';
|
||||
return { classes: 'mt-0 mb-0 !py-0.5', showUsername: false };
|
||||
} else {
|
||||
return 'mt-0 pt-0.5 pb-1';
|
||||
return { classes: 'mt-0 pt-0.5 pb-1', showUsername: false };
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return {classes: '', showUsername: true };
|
||||
},
|
||||
checkForMentions() {
|
||||
const input = this.$refs.messageBox as HTMLTextAreaElement;
|
||||
@@ -342,7 +343,7 @@ export default {
|
||||
completeMention(user: SafeUser) {
|
||||
this.messageContent = this.messageContent.replace('@' + this.search.content, `<@${user.id}>`);
|
||||
this.search.show = false;
|
||||
this.$refs.messageBox.focus();
|
||||
(this.$refs.messageBox as HTMLInputElement).focus();
|
||||
},
|
||||
async listenToWebsocket(conversationDiv: HTMLElement) {
|
||||
let { $io } = useNuxtApp();
|
||||
@@ -357,17 +358,12 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
message.body = parseMessageBody(message.body, this.participants);
|
||||
|
||||
if (this.channel.messages.find((e) => e.id === message.id)) {
|
||||
// message is already in the server, replace it with the updated message
|
||||
useActiveStore().updateMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.creator.id === this.user?.id) return;
|
||||
|
||||
|
||||
if (!document.hasFocus()) {
|
||||
new Notification(`Message from @${message.creator.username}`, { body: message.body, tag: this.channel.id.toString() });
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<template>
|
||||
<nav class="bg-[var(--primary-bg)] h-screen p-4 grid grid-cols-1 grid-rows-[56px_1fr_56px] shadow shadow-black/80">
|
||||
<div>
|
||||
<nuxt-link to="/channel/@me">
|
||||
<nuxt-link
|
||||
to="/channel/@me"
|
||||
draggable="false"
|
||||
>
|
||||
<button
|
||||
class="bg-[var(--tertiary-bg)] p-3 rounded-full transition-all hover:rounded-[1.375rem] ease-in-out hover:bg-[var(--tertiary-lightened-bg)] duration-300"
|
||||
class="bg-[var(--tertiary-bg)] p-3 transition-all ease-in-out hover:bg-[var(--tertiary-lightened-bg)] duration-300"
|
||||
:class="(activeConversation.type === 'dm') ? 'rounded-[1.375rem]' : 'rounded-full hover:rounded-[1.375rem]'"
|
||||
aria-label="Home"
|
||||
>
|
||||
<span>
|
||||
<svg
|
||||
@@ -51,9 +56,12 @@
|
||||
v-for="server in servers"
|
||||
:key="server.id"
|
||||
:to="'/channel/' + server.channels[0]?.id"
|
||||
draggable="false"
|
||||
>
|
||||
<button
|
||||
class="bg-[var(--tertiary-bg)] p-3 rounded-full transition-all hover:rounded-[1.375rem] ease-in-out hover:bg-[var(--tertiary-lightened-bg)] duration-300 h-[56px] w-[56px]"
|
||||
class="bg-[var(--tertiary-bg)] p-3 transition-all ease-in-out hover:bg-[var(--tertiary-lightened-bg)] duration-300 h-[56px] w-[56px]"
|
||||
:class="(activeConversation.type === 'server' && activeConversation.server.server.id === server.id) ? 'rounded-[1.375rem]' : 'rounded-full hover:rounded-[1.375rem]'"
|
||||
:aria-label="server.name"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -73,6 +81,7 @@
|
||||
|
||||
<button
|
||||
class="p-3 rounded-full transition-colors ease-in-out hover:bg-[var(--tertiary-lightened-bg)] duration-300 cursor-pointer"
|
||||
@click="createServerModalOpen = true"
|
||||
>
|
||||
<svg
|
||||
width="32"
|
||||
@@ -89,17 +98,71 @@
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<Modal
|
||||
:opened="createServerModalOpen"
|
||||
@close="createServerModalOpen = false"
|
||||
>
|
||||
<div
|
||||
class="bg-[var(--secondary-bg)] rounded-xl shadow-2xl flex flex-row overflow-hidden z-20 absolute border border-[var(--tertiary-bg)] -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2"
|
||||
>
|
||||
<img
|
||||
src="/eberhard-grossgasteiger-eBXIZe1DU7Y-unsplash.jpg"
|
||||
class="h-96 w-64 object-cover"
|
||||
/>
|
||||
<div class="p-4 flex flex-col text-center">
|
||||
<h1 class="font-semibold text-2xl">
|
||||
Create Server
|
||||
</h1>
|
||||
<form
|
||||
class="flex flex-col gap-y-3 my-2"
|
||||
@submit.prevent="createServer"
|
||||
>
|
||||
<input
|
||||
v-model="serverName"
|
||||
class="px-4 py-2 rounded-md w-full bg-[var(--primary-input)] shadow-2xl placeholder:text-[var(--primary-placeholder)] focus:outline-none"
|
||||
name="name"
|
||||
placeholder="Server Name"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
value="Submit"
|
||||
class="w-full bg-[#5865F2] py-2 px-4 rounded-md cursor-pointer"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useActiveStore } from '~/stores/activeStore';
|
||||
import { useServerStore } from '~/stores/serverStore';
|
||||
import { IServer } from '~/types';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
servers: storeToRefs(useServerStore()).servers
|
||||
createServerModalOpen: false,
|
||||
serverName: '',
|
||||
servers: storeToRefs(useServerStore()).servers,
|
||||
activeConversation: {
|
||||
type: storeToRefs(useActiveStore()).type,
|
||||
server: storeToRefs(useActiveStore()).server
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async createServer() {
|
||||
const serverStore = useServerStore();
|
||||
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
|
||||
const server: IServer = await $fetch('/api/channels/create', { method: 'post', body: { serverName: this.serverName }, headers });
|
||||
this.createServerModalOpen = false;
|
||||
this.serverName = '';
|
||||
serverStore.addServer(server);
|
||||
|
||||
navigateTo(`/channel/${server.channels[0].id}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,13 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="opened"
|
||||
class="z-10 bg-[var(--primary-dark)] w-fit rounded-md shadow-md p-3"
|
||||
class="z-10 bg-[var(--secondary-bg)] w-fit rounded-lg shadow-md border border-[var(--tertiary-bg)] overflow-hidden"
|
||||
>
|
||||
<div class="max-w-[350px] max-h-[450px] overflow-hidden">
|
||||
<div class="max-w-[374px] max-h-[475px] overflow-hidden">
|
||||
<EmojiPicker
|
||||
v-if="openedBy === 'emojiPicker'"
|
||||
@picked-emoji="$emit('picked-emoji', $event)"
|
||||
/>
|
||||
<UserProfile
|
||||
v-else
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -16,10 +19,9 @@
|
||||
export default {
|
||||
props: {
|
||||
openedBy: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
opened: Boolean
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -292,6 +292,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
:opened="createChannelModelOpen"
|
||||
@close="createChannelModelOpen = false"
|
||||
>
|
||||
<div
|
||||
class="bg-[var(--secondary-bg)] rounded-xl shadow-2xl flex flex-row overflow-hidden z-20 absolute border border-[var(--tertiary-bg)] -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2"
|
||||
>
|
||||
<img
|
||||
src="/ryan-klaus-5CkzYaubjkk-unsplash.jpg"
|
||||
class="h-96 w-64 object-cover"
|
||||
/>
|
||||
<div class="p-4 flex flex-col text-center">
|
||||
<h1 class="font-semibold text-2xl">
|
||||
Create Channel
|
||||
</h1>
|
||||
<form
|
||||
class="flex flex-col gap-y-3 my-2"
|
||||
@submit.prevent="createChannel"
|
||||
>
|
||||
<input
|
||||
v-model="channelName"
|
||||
class="px-4 py-2 rounded-md w-full bg-[var(--primary-input)] shadow-2xl placeholder:text-[var(--primary-placeholder)] focus:outline-none"
|
||||
name="name"
|
||||
placeholder="Channel Name"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
value="Submit"
|
||||
class="w-full bg-[#5865F2] py-2 px-4 rounded-md cursor-pointer"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -319,10 +354,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
userIsOwner() {
|
||||
return this.activeServer.data.server && this.activeServer.type === 'server' && this.activeServer.data.server.roles?.find((e: IRole) => e.users.some((el) => el.id === this.user?.id))?.owner;
|
||||
return this.activeServer.type === 'server' && this.activeServer.data.server.participants.find((e) => e.id === this.user?.id)?.roles?.some((e) => e.owner === true);
|
||||
},
|
||||
userIsAdmin() {
|
||||
return this.activeServer.data.server && this.activeServer.type === 'server' && this.activeServer.data.server.roles?.find((e: IRole) => e.users.some((el) => el.id === this.user?.id))?.administer;
|
||||
return this.activeServer.type === 'server' && this.activeServer.data.server.participants.find((e) => e.id === this.user?.id)?.roles?.some((e) => e.administer === true);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -337,12 +372,14 @@ export default {
|
||||
|
||||
useServerStore().addChannel(this.activeServer.data.server.id, channel);
|
||||
this.createChannelModelOpen = false;
|
||||
|
||||
navigateTo(`/channel/${channel.id}`);
|
||||
},
|
||||
async createInvite() {
|
||||
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
|
||||
const inviteCode = await $fetch(`/api/guilds/${this.activeServer.data.server.id}/createInvite`, { method: 'POST', headers });
|
||||
},
|
||||
async logout() {
|
||||
logout() {
|
||||
useUserStore().logout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,138 @@
|
||||
<template>
|
||||
<div />
|
||||
<div class="w-[374px] max-h-[475px] overflow-y-scroll">
|
||||
<div class="relative h-[calc(160px+56px)]">
|
||||
<div class="w-full h-40 absolute">
|
||||
<img
|
||||
src="/tansu-topuzoglu-v2mlqhy5dLU-unsplash.jpg"
|
||||
class="h-40 w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-[28%] aspect-square bg-[var(--primary-bg)] border-2 border-[var(--tertiary-bg)] rounded-xl overflow-hidden left-1/2 -translate-x-1/2 top-28 z-10 absolute">
|
||||
<img
|
||||
src="/daiga-ellaby-snUtnGUp2zU-unsplash.jpg"
|
||||
class="h-40 w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 pb-2">
|
||||
<div class="text-center">
|
||||
<p class="font-semibold">
|
||||
{{ user.username }}
|
||||
</p>
|
||||
</div>
|
||||
<hr class="border-[var(--tertiary-lightened-bg)] my-2" />
|
||||
<div class="m-1 p-2 rounded-lg bg-[var(--tertiary-bg)] flex flex-col gap-y-1">
|
||||
<div v-if="true">
|
||||
<p class="font-semibold text-sm">
|
||||
About Me
|
||||
</p>
|
||||
<div class="text-sm p-1">
|
||||
<p>lorem ipsum</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-sm">
|
||||
Member since
|
||||
</p>
|
||||
<div class="text-sm p-1">
|
||||
<p>
|
||||
{{
|
||||
new Date(user.createdAt).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isDm"
|
||||
class="mb-2"
|
||||
>
|
||||
<p
|
||||
v-if="roles.length < 1"
|
||||
class="font-semibold text-sm"
|
||||
>
|
||||
No Roles
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="user.id !== userData.id">
|
||||
<input
|
||||
v-model="message"
|
||||
class="bg-[var(--secondary-bg)] placeholder:text-[var(--primary-placeholder)] px-2 focus:outline-none py-1 rounded-md w-full border border-[var(--tertiary-lightened-bg)]"
|
||||
:placeholder="`Message @${user.username}`"
|
||||
@keypress.enter="sendDM()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!--
|
||||
<script>
|
||||
import { IUser } from '~/types';
|
||||
|
||||
<script lang="ts">
|
||||
import { useActiveStore } from '~/stores/activeStore';
|
||||
import { useDmStore } from '~/stores/dmStore';
|
||||
import { useEmojiPickerStore } from '~/stores/emojiPickerStore';
|
||||
import { useUserStore } from '~/stores/userStore';
|
||||
import { IUser, IRole } from '~/types';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
user: {
|
||||
type: IUser,
|
||||
required: true
|
||||
async setup() {
|
||||
const userData = useUserStore().user;
|
||||
async function fetchUser() {
|
||||
const emojiPickerData = useEmojiPickerStore().emojiPickerData;
|
||||
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
|
||||
const activeServer = useActiveStore().server;
|
||||
|
||||
const isDm = useRoute().path.includes('@me');
|
||||
|
||||
let user: IUser | null;
|
||||
|
||||
if (isDm) {
|
||||
user = await $fetch(`/api/user/${emojiPickerData.userId}/profile`, { headers }) as IUser | null;
|
||||
} else {
|
||||
user = await $fetch(`/api/user/${emojiPickerData.userId}/${activeServer.server.id}/profile`, { headers }) as IUser | null;
|
||||
}
|
||||
|
||||
return { user, isDm };
|
||||
}
|
||||
|
||||
const { user, isDm } = await fetchUser();
|
||||
|
||||
if (!user) return;
|
||||
|
||||
return { user, isDm, fetchUser, userData };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
message: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
roles(): IRole[] {
|
||||
return this.user.roles?.filter((e: IRole) => e.owner === false) || [];
|
||||
},
|
||||
userIsOwner(): boolean {
|
||||
return this.user.roles?.some((e: IRole) => e.owner === true) || false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async sendDM() {
|
||||
if (!this.message.trim()) return;
|
||||
|
||||
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
|
||||
const preExistingDM = useDmStore().getByPartnerId(this.user.id);
|
||||
|
||||
if (preExistingDM && useRoute().path !== `/channel/@me/${preExistingDM.id}`) {
|
||||
await navigateTo(`/channel/@me/${preExistingDM.id}`);
|
||||
}
|
||||
|
||||
await $fetch(`/api/channels/${preExistingDM.id}/sendMessage`, { method: 'post', body: { body: this.message }, headers });
|
||||
this.message = '';
|
||||
|
||||
useEmojiPickerStore().closeEmojiPicker();
|
||||
},
|
||||
}
|
||||
};
|
||||
</script> -->
|
||||
</script>
|
||||
Reference in New Issue
Block a user