more actions, new colors (very bad going to change)

This commit is contained in:
Zoe
2023-01-19 00:59:43 -06:00
parent 3cdf7758eb
commit bf5245bec4
17 changed files with 558 additions and 355 deletions

View File

@@ -1,3 +1,25 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root {
--background-color: hsl(230,28%,7.3%);
--foreground-color: hsl(230,26%,13%);
--primary-accent: hsl(180,55%,45%);
--message-input-color: hsl(228,27.3%,25%);
--primary-placeholder: hsl(180,25%,65%);
--primary-dark: hsl(225, 7.7%, 10.2%); /* dropdown and emoji picker bg */
--primary-700: hsl(230,31.2%,6.3%); /* code block border */
--primary-600: hsl(220, 6.8%, 17.3%); /* modal bg */
--primary-500: hsl(230,28.7%,9.8%); /* reaction button bg, code block, and inline code bg */
--primary-400: hsl(230, 12%, 19.2%); /* action buttons */
--primary-300: hsl(230,26%,15%); /* nav button bg */
--primary-200: hsl(230,26%,21.3%); /* nav button hover bg */
--primary-text: hsl(216, 3.7%, 73.5%); /* main text color (duh) */
--reaction-border: hsl(230,33.4%,18.7%); /* reaction border on hover */
--reaction-hover: hsl(230,31.2%,12.5%); /* reaction bg on hover */
--invite-members: var(--primary-accent); /* color of dot next to server members count on invites */
--primary-danger: hsl(359, 66.7%, 54.1%);
}

View File

@@ -1,8 +1,8 @@
<template> <template>
<li> <li>
<button <button
class="w-full cursor-pointer bg-[hsl(225,7.7%,10.2%)] hover:bg-[hsl(225,7.7%,17.4%)] text-left px-3 py-1.5 rounded-md flex items-center justify-between transition-colors" class="w-full cursor-pointer bg-inherit hover:backdrop-brightness-150 text-left px-3 py-1.5 rounded-md flex items-center justify-between transition-all"
:class="(danger) ? 'hover:bg-[hsl(359,66.7%,54.1%)]' : ''"> :class="(danger) ? 'hover:bg-[var(--primary-danger)]' : ''">
<slot /> <slot />
</button> </button>
</li> </li>

View File

@@ -1,6 +1,6 @@
<template> <template>
<Transition name="pop-in"> <Transition name="pop-in">
<div ref="dropdown" class="z-[2] absolute m-2 bg-[hsl(225,7.7%,10.2%)] w-[calc(100%-1rem)] p-3 rounded text-left" <div ref="dropdown" class="z-[2] absolute m-2 bg-[var(--primary-dark)] w-[calc(100%-1rem)] p-3 rounded text-left"
:class="(inverted) ? 'dropdown-inverse' : 'dropdown'" :class="(inverted) ? 'dropdown-inverse' : 'dropdown'"
v-if="opened"> v-if="opened">
<slot /> <slot />

View File

@@ -42,11 +42,11 @@ export default {
<template> <template>
<div v-if="opened" <div v-if="opened"
class="rounded-lg shadow-md p-3 z-10 bg-[hsl(223,6.8%,19.8%)]"> class="rounded-lg shadow-md p-3 z-10 bg-[var(--primary-dark)]">
<div class="py-1.5 flex flex-col"> <div class="py-1.5 flex flex-col">
<div class="flex-row gap-x-2 overflow-x-scroll"> <div class="flex-row gap-x-2 overflow-x-scroll">
<button @click="scrollTo('people')" <button @click="scrollTo('people')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -62,7 +62,7 @@ export default {
</svg> </svg>
</button> </button>
<button @click="scrollTo('nature')" <button @click="scrollTo('nature')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -76,7 +76,7 @@ export default {
</svg> </svg>
</button> </button>
<button @click="scrollTo('food')" <button @click="scrollTo('food')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -90,7 +90,7 @@ export default {
</svg> </svg>
</button> </button>
<button @click="scrollTo('activities')" <button @click="scrollTo('activities')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -107,7 +107,7 @@ export default {
</svg> </svg>
</button> </button>
<button @click="scrollTo('travel')" <button @click="scrollTo('travel')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -121,7 +121,7 @@ export default {
</svg> </svg>
</button> </button>
<button @click="scrollTo('objects')" <button @click="scrollTo('objects')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -139,7 +139,7 @@ export default {
</svg> </svg>
</button> </button>
<button @click="scrollTo('symbols')" <button @click="scrollTo('symbols')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -157,7 +157,7 @@ export default {
</svg> </svg>
</button> </button>
<button @click="scrollTo('flags')" <button @click="scrollTo('flags')"
class="p-1.5 hover:bg-[hsl(223,6.8%,25.3%)] rounded-md transition-colors"> class="p-1.5 bg-inherit hover:backdrop-brightness-125 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -175,15 +175,15 @@ export default {
<div class="max-w-[375px] max-h-[450px] overflow-hidden overflow-y-scroll scroll-smooth EmojiPicker" <div class="max-w-[375px] max-h-[450px] overflow-hidden overflow-y-scroll scroll-smooth EmojiPicker"
id="emoji-pane"> id="emoji-pane">
<div class="text-black flex flex-col category" <div class="text-black flex flex-col category bg-[var(--primary-dark)]"
v-for="category in categories"> v-for="category in categories">
<h6 class="uppercase text-[hsl(216,3.7%,73.5%)] sticky top-0 bg-[hsl(223,6.8%,19.8%)] py-1">{{ <h6 class="uppercase text-[var(--primary-text)] sticky top-0 bg-inherit z-10 py-1">{{
category.name category.name
}}</h6> }}</h6>
<div class="flex flex-wrap" <div class="flex flex-wrap"
:id="category.name"> :id="category.name">
<button v-for="emoji in category.emojis" <button v-for="emoji in category.emojis"
class="p-2 rounded hover:bg-[hsl(223,6.8%,28.4%)] h-12 transition-colors emoji" class="p-2 rounded bg-inherit hover:backdrop-brightness-[1.45] h-12 transition-all emoji"
@click="$emit('pickedEmoji', emoji.short_name)" @click="$emit('pickedEmoji', emoji.short_name)"
:aria-label='emoji.name.toLowerCase()'> :aria-label='emoji.name.toLowerCase()'>
<span :style="emojiStyles(emoji.short_name, 32)" <span :style="emojiStyles(emoji.short_name, 32)"

View File

@@ -1,11 +1,11 @@
<template> <template>
<div class="w-6/12 bg-[hsl(223,6.9%,19.8%)] mb-1 mt-0.5 p-4 rounded-md shadow-md mr-2"> <div class="w-6/12 bg-[var(--primary-500)] mb-1 mt-0.5 p-4 rounded-md shadow-md mr-2">
<p class="text-sm font-semibold text-zinc-100">You've been invited to join a <p class="text-sm font-semibold text-zinc-100">You've been invited to join a
server</p> server</p>
<span class="text-xl font-bold capitalize leading-loose">{{ invite.server.name }}</span> <span class="text-xl font-bold capitalize leading-loose">{{ invite.server.name }}</span>
<div class="flex items-center"> <div class="flex items-center">
<span <span
class="before:bg-[hsl(214,9.9%,50.4%)] before:h-2 before:w-2 before:inline-block before:my-auto before:rounded-full before:mr-1"></span> class="before:bg-[var(--invite-members)] before:h-2 before:w-2 before:inline-block before:my-auto before:rounded-full before:mr-1"></span>
<span>{{ invite.server.participants.length }} Members</span> <span>{{ invite.server.participants.length }} Members</span>
</div> </div>
<div class="flex w-full justify-end"> <div class="flex w-full justify-end">

View File

@@ -1,14 +1,18 @@
<template> <template>
<div class="absolute right-0 mr-10 -top-[20px] h-fit opacity-0 pointer-events-none action-buttons z-10" <div class="relative message-wrapper"
:class="(emojiPickerOpen) ? 'opacity-100 pointer-events-auto' : ''"> @mouseenter="mouseEnter()"
<div class="absolute right-[38px] top-0 w-[375px]"> @mouseleave="mouseLeave()">
<EmojiPicker v-on:pickedEmoji="pickedEmoji($event)" <div class="absolute right-0 mr-10 -top-[20px] h-fit opacity-0 pointer-events-none action-buttons z-[5]"
:opened="emojiPickerOpen" /> :class="(emojiPickerOpen) ? 'opacity-100 pointer-events-auto' : ''">
</div> <div class="absolute top-0 w-[375px]"
<div class="relative"> :style="emojiPickerStyles">
<div @click="emojiPickerOpen = !emojiPickerOpen" <EmojiPicker v-on:pickedEmoji="pickedEmoji($event)"
class="bg-[hsl(220,calc(1*7.7%),22.9%)] hover:bg-[hsl(220,calc(1*7.7%),28.6%)] transition-colors border border-[rgb(32,34,37)] rounded-md flex text-[hsl(216,3.7%,73.5%)] w-fit h-fit"> :opened="emojiPickerOpen" />
<button class="p-1"> </div>
<div id="actions"
class="relative bg-[var(--primary-400)] rounded-md border border-[rgb(32,34,37)] text-[var(--primary-text)] flex overflow-hidden">
<button @click="emojiPickerOpen = !emojiPickerOpen"
class="p-1 hover:backdrop-brightness-125 transition-all flex w-fit h-fit">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="20" width="20"
height="20" height="20"
@@ -21,37 +25,92 @@
d="m13 19l-1 1l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 0 1 8.003 5.996M14 16h6m-3-3v6" /> d="m13 19l-1 1l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 0 1 8.003 5.996M14 16h6m-3-3v6" />
</svg> </svg>
</button> </button>
<button v-if="!actionButtonOverflowMenuOpen"
@click="actionButtonOverflowMenuOpen = true"
class="p-1 hover:backdrop-brightness-125 transition-all flex w-fit h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<circle cx="5"
cy="12"
r="1" />
<circle cx="12"
cy="12"
r="1" />
<circle cx="19"
cy="12"
r="1" />
</g>
</svg>
</button>
<div @click="actionButtonOverflowMenuOpen = false"
v-if="actionButtonOverflowMenuOpen"
class="flex">
<button @click="copy(message.id)"
class="p-1 hover:backdrop-brightness-125 transition-all flex text-[var(--primary-400)] w-[28px] h-[28px] items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg"
class="bg-[var(--primary-text)] rounded"
width="18"
height="18"
viewBox="0 0 24 24">
<path fill="currentColor"
d="M10 7v2H9v6h1v2H6v-2h1V9H6V7h4m6 0a2 2 0 0 1 2 2v6c0 1.11-.89 2-2 2h-4V7m4 2h-2v6h2V9Z" />
</svg>
</button>
<button v-if="message.creator.id === user.id"
@click="deleteMessage()"
class="p-1 hover:backdrop-brightness-125 transition-all flex text-[var(--primary-danger)] w-[28px] h-[28px] items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
</div>
</div> </div>
</div> </div>
</div> <div class="transition-[backdrop-filter] hover:backdrop-brightness-90 ease-[cubic-bezier(.37,.64,.59,.33)] duration-150 my-4 px-7 py-2 message-wrapper items-center z-[1]"
<div class="transition-[backdrop-filter] hover:backdrop-brightness-90 ease-[cubic-bezier(.37,.64,.59,.33)] duration-150 my-4 px-7 py-2 message-wrapper items-center" :class="classes">
:class="classes"> <div class="message-content">
<div class="message-content"> <div class="message-sender-text">
<div class="message-sender-text"> <p class="mb-1 font-semibold w-fit"
<p class="mb-1 font-semibold w-fit" v-if="showUsername">
v-if="showUsername"> {{ message.creator.username }}
{{ message.creator.username }} </p>
</p> <p class="break-words max-w-full"
<p class="break-words max-w-full" v-html="message.body"></p> v-html="message.body"></p>
</div> </div>
<div v-for="invite in message.invites"> <div v-for="invite in message.invites">
<InviteCard :invite="invite" /> <InviteCard :invite="invite" />
</div> </div>
<div class="flex gap-2 flex-wrap"> <div class="flex gap-2 flex-wrap">
<button @click="toggleReaction(reaction.emoji.name)" <button @click="toggleReaction(reaction.emoji.name)"
v-for="reaction in message.reactions" v-for="reaction in message.reactions"
class="py-0.5 px-1.5 bg-[hsl(223,6.9%,19.8%)] border items-center flex rounded-lg border-[hsl(223,6.9%,19.8%)] hover:border-[hsl(223,6.9%,33.3%)] hover:bg-[hsl(223,6.9%,21.3%)] transition-colors shadow-sm max-h-[30px]" class="py-0.5 px-1.5 bg-[var(--primary-500)] border items-center flex rounded-lg border-[var(--primary-500)] hover:border-[var(--reaction-border)] hover:bg-[var(--reaction-hover)] transition-colors shadow-sm max-h-[30px]"
:class="(reaction.users.find((e) => e.id === user.id)) ? 'border-[rgb(88,101,242)] hover:border-[rgb(88,101,242)]' : ''"> :class="(reaction.users.find((e) => e.id === user.id)) ? '!border-[rgb(88,101,242)] hover:!border-[rgb(88,101,242)]' : ''">
<div class="flex items-center mr-0.5 w-6 drop-shadow"> <div class="flex items-center mr-0.5 w-6 drop-shadow">
<span :style="emojiStyles(reaction.emoji.name, 16)"></span> <span :style="emojiStyles(reaction.emoji.name, 16)"></span>
</div>
<div class="relative overflow-hidden ml-1.5">
<div class="min-w-[9px] h-6"
:key="reaction.count">
<span class="dropshadow-sm">{{ reaction.count }}</span>
</div> </div>
</div> <div class="relative overflow-hidden ml-1.5">
</button> <div class="min-w-[9px] h-6"
:key="reaction.count">
<span class="dropshadow-sm">{{ reaction.count }}</span>
</div>
</div>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -61,6 +120,7 @@
import { PropType } from 'vue'; import { PropType } from 'vue';
import { IMessage } from '~/types'; import { IMessage } from '~/types';
import { useGlobalStore } from '~/stores/store'; import { useGlobalStore } from '~/stores/store';
import { useClipboard } from '@vueuse/core'
import emojiJson from '~/assets/json/emoji.json'; import emojiJson from '~/assets/json/emoji.json';
export default { export default {
@@ -82,6 +142,15 @@ export default {
return { return {
user: storeToRefs(useGlobalStore()).user, user: storeToRefs(useGlobalStore()).user,
emojiPickerOpen: false, emojiPickerOpen: false,
emojiPickerStyles: this.calculateEmojiPickerRight(),
actionButtonOverflowMenuOpen: false,
}
},
setup() {
const { text, copy, copied, isSupported } = useClipboard()
return {
copy
} }
}, },
methods: { methods: {
@@ -96,7 +165,7 @@ export default {
emojiStyles(emoji: string, width: number) { emojiStyles(emoji: string, width: number) {
const emojis = emojiJson.filter((e) => e.has_img_twitter) const emojis = emojiJson.filter((e) => e.has_img_twitter)
const twemoji = emojis.find((e) => e.emoji === emoji) const twemoji = emojis.find((e) => e.emoji === emoji)
if (twemoji === undefined || twemoji.sheet_x === undefined || twemoji.sheet_y === undefined) { if (twemoji === undefined || twemoji.sheet_x === undefined || twemoji.sheet_y === undefined) {
return {}; return {};
} }
const sheet_x = (twemoji.sheet_y * (32 + 2)) / 2; const sheet_x = (twemoji.sheet_y * (32 + 2)) / 2;
@@ -117,6 +186,37 @@ export default {
this.toggleReaction(replacementEmoji.emoji) this.toggleReaction(replacementEmoji.emoji)
this.emojiPickerOpen = false; this.emojiPickerOpen = false;
}, },
async deleteMessage() {
const route = useRoute()
await $fetch(`/api/channels/${route.params.id}/messages/${this.message.id}/delete`, { method: "POST" })
},
calculateEmojiPickerRight() {
const actions = document.getElementById('actions')
if (!actions) return {}
const right = actions.clientWidth + 8
return {
right: right + 'px'
}
},
keyPressed(ev: KeyboardEvent) {
if (ev.key === 'Shift') {
this.actionButtonOverflowMenuOpen = true
}
},
keyUnpressed(ev: KeyboardEvent) {
if (ev.key === 'Shift') {
this.actionButtonOverflowMenuOpen = false
}
},
mouseEnter() {
document.body.addEventListener('keydown', this.keyPressed, false);
document.body.addEventListener('keyup', this.keyUnpressed, false);
},
mouseLeave() {
this.actionButtonOverflowMenuOpen = false
document.body.removeEventListener('keydown', this.keyPressed, false)
document.body.removeEventListener('keyup', this.keyUnpressed, false)
}
} }
} }
</script> </script>
@@ -128,8 +228,8 @@ export default {
} }
pre.codeblock { pre.codeblock {
background-color: hsl(223, 6.9%, 19.8%); background-color: var(--primary-500);
border: 1px solid hsl(216, 7.2%, 13.5%); border: 1px solid var(--primary-700);
border-radius: 0.375rem; border-radius: 0.375rem;
white-space: prewrap; white-space: prewrap;
margin-top: 4px; margin-top: 4px;
@@ -147,10 +247,9 @@ pre.codeblock code {
} }
code.inline-code { code.inline-code {
background-color: hsl(223, 6.9%, 19.8%); background-color: var(--primary-500);
padding: 0.2rem; padding: 0.2rem;
font-size: 85%; font-size: 85%;
border-radius: 4px; border-radius: 4px;
} }
</style> </style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="h-full bg-[hsl(220,calc(1*7.7%),22.9%)] relative text-white"> <div class="h-full relative text-white bg-[var(--background-color)] grid grid-rows-[48px_1fr]">
<div class="bg-[hsl(220,calc(1*7.7%),22.9%)] absolute w-full shadow px-4 py-3 z-[1] shadow-zinc-900/50"> <div class="w-full px-4 py-3 z-[1]">
<div v-if="!server.DM" <div v-if="!server.DM"
class="flex items-center"> class="flex items-center">
<span class="mr-1"> <span class="mr-1">
@@ -40,77 +40,82 @@
}}</span> }}</span>
</div> </div>
</div> </div>
<div class="w-full h-[calc(100%-76px-48px)] top-[48px] absolute overflow-y-scroll pb-1"
id="conversation-pane"> <section
<div> class="bg-[var(--foreground-color)] my-3 mx-1 h-[calc(100%-24px)] overflow-hidden rounded-lg relative grid grid-rows-[1fr_70px]">
<div v-if="server.messages.length === 0"> <div class="h-full overflow-y-scroll" id="conversation-pane">
<p>No messages yet</p> <div class="w-full pb-1 bg-inherit">
<div>
<div v-if="server.messages.length === 0">
<p>No messages yet</p>
</div>
<Message v-else
v-for="(message, i) in server.messages"
:message="message"
:classes="calculateMessageClasses(message, i)"
:showUsername="i === 0 || server.messages[i - 1]?.creator.id !== message.creator.id" />
</div>
</div> </div>
<div v-else <div v-if="showSearch"
v-for="(message, i) in server.messages" class="relative message-wrapper"> class="absolute bottom-[calc(75px+0.5rem)] mx-4 w-[calc(100vw-88px-240px-32px)] py-3 px-4 bg-[var(--primary-500)] rounded-lg shadow-md z-5">
<Message :message="message" <div class="relative flex flex-col">
:classes="calculateMessageClasses(message, i)" <div v-for="user in searchResults"
:showUsername="i === 0 || server.messages[i - 1]?.creator.id !== message.creator.id" /> class="mx-2 my-1 w-[calc(100vw-88px-240px-64px-16px)] px-4 py-3 hover:backdrop-brightness-125 select-none rounded-md transition-all"
@click="completeMention(user)">
{{ user.username }}
</div>
</div>
</div> </div>
</div> </div>
</div>
<div v-if="showSearch" <div class="flex absolute flex-row bottom-0 w-full h-fit bg-inherit mb-1">
class="absolute bottom-[calc(75px+0.5rem)] mx-4 w-[calc(100vw-88px-240px-32px)] py-3 px-4 bg-[hsl(223,6.9%,19.8%)] rounded-lg shadow-md z-5"> <form @keyup="checkForMentions"
<div class="relative flex flex-col"> @keypress="typing($event)"
<div v-for="user in searchResults" @submit.prevent="sendMessage"
class="mx-2 my-1 w-[calc(100vw-88px-240px-64px-16px)] px-4 py-3 hover:bg-[hsl(223,6.9%,24.3%)] select-none rounded-md transition-colors" @keydown.enter.exact.prevent="sendMessage"
@click="completeMention(user)"> class="relative px-4 w-full pt-1.5 h-fit pb-1">
{{ user.username }} <div id="textbox"
</div> class="px-4 rounded-md w-full min-h-[44px] h-fit bg-[var(--message-input-color)] placeholder:text-[var(--primary-placeholder)] flex flex-row">
</div> <textarea type="text"
</div> id="messageBox"
<div class="conversation-input w-[calc(100vw-88px-240px)] h-fit"> class="bg-transparent focus:outline-none py-2 w-full resize-none leading-relaxed h-[44px]"
<form @keyup="checkForMentions" cols="1"
@keypress="typing($event)" v-model="messageContent"
@submit.prevent="sendMessage" placeholder="Send a Message..." />
@keydown.enter.exact.prevent="sendMessage" <input type="submit"
class="relative px-4 w-full pt-1.5 h-fit pb-1"> class="absolute -top-full -left-full invisible"
<div id="textbox" id="submit">
class="px-4 rounded-md w-full min-h-[44px] h-fit bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[hsl(218,calc(1*4.6%),46.9%)] flex flex-row"> <label for="submit"
<textarea type="text" class="py-1 px-1.5 h-fit my-auto cursor-pointer"
id="messageBox" role="button"><svg width="32"
class="bg-transparent focus:outline-none py-2 w-full resize-none leading-relaxed h-[44px]" height="26"
cols="1" viewBox="0 0 24 24">
v-model="messageContent" <path fill="none"
placeholder="Send a Message..." /> stroke="currentColor"
<input type="submit" stroke-linecap="round"
class="absolute -top-full -left-full invisible" stroke-linejoin="round"
id="submit"> stroke-width="2"
<label for="submit" d="M10 14L21 3m0 0l-6.5 18a.55.55 0 0 1-1 0L10 14l-7-3.5a.55.55 0 0 1 0-1L21 3" />
class="py-1 px-1.5 h-fit my-auto cursor-pointer" </svg></label>
role="button"><svg width="32" </div>
height="32" <div class="w-full h-4">
viewBox="0 0 24 24"> <p class="text-sm"
<path fill="none" v-if="usersTyping.length > 0">
stroke="currentColor" <span v-if="usersTyping.length < 4">
stroke-linecap="round" <span v-for="(username, i) in usersTyping"
stroke-linejoin="round" class="font-semibold">
stroke-width="2" <span v-if="i === usersTyping.length - 1 && usersTyping.length > 1">and </span>
d="M10 14L21 3m0 0l-6.5 18a.55.55 0 0 1-1 0L10 14l-7-3.5a.55.55 0 0 1 0-1L21 3" /> {{ username }}
</svg></label> <span v-if="i !== usersTyping.length - 1 && usersTyping.length > 1">, </span>
</div> </span>
<div class="w-full h-4"> is typing
<p class="text-sm"
v-if="usersTyping.length > 0">
<span v-if="usersTyping.length < 4">
<span v-for="(username, i) in usersTyping"
class="font-semibold">
<span v-if="i === usersTyping.length - 1 && usersTyping.length > 1">and </span>
{{ username }}
<span v-if="i !== usersTyping.length - 1 && usersTyping.length > 1">, </span>
</span> </span>
is typing <span v-else>Several users are typing</span>
</span> </p>
<span v-else>Several users are typing</span> </div>
</p> </form>
</div> </div>
</form> </section>
</div>
</div> </div>
</template> </template>
@@ -274,8 +279,13 @@ export default {
listenToWebsocket(conversationDiv: HTMLElement) { listenToWebsocket(conversationDiv: HTMLElement) {
this.socket.removeAllListeners(); this.socket.removeAllListeners();
this.socket.on(`message-${this.server.id}`, (ev: { message: IMessage }) => { this.socket.on(`message-${this.server.id}`, (ev: { message: IMessage, deleted?: boolean }) => {
let { message } = ev let { message, deleted } = ev
if (deleted) {
useGlobalStore().removeMessage(message.id)
return;
}
message.body = parseMessageBody(message.body, useGlobalStore().activeChannel) message.body = parseMessageBody(message.body, useGlobalStore().activeChannel)
@@ -330,15 +340,3 @@ export default {
}, },
} }
</script> </script>
<style scoped>
.conversation-input {
display: flex;
position: fixed;
flex-direction: row;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
background-color: hsl(220, calc(1 * 7.7%), 22.9%);
bottom: calc(0px - 0.5rem);
}
</style>

View File

@@ -1,10 +1,10 @@
<template> <template>
<nav <nav
class="p-4 bg-[hsl(216,calc(1*7.2%),13.5%)] grid grid-cols-1 grid-rows-[56px_1fr_56px] h-screen min-w-[88px] text-white relative"> class="p-4 bg-[var(--background-color)] grid grid-cols-1 grid-rows-[56px_1fr_56px] h-screen min-w-[88px] text-white relative">
<div> <div>
<nuxt-link to="/channel/@me"> <nuxt-link to="/channel/@me">
<div <button
class="bg-zinc-600/80 p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-zinc-500/60 duration-300"> class="bg-[var(--primary-300)] p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-[var(--primary-200)] duration-300">
<span> <span>
<svg width="32" <svg width="32"
height="32" height="32"
@@ -29,14 +29,17 @@
d="M12 12c2-2.96 0-7-1-8c0 3.038-1.773 4.741-3 6c-1.226 1.26-2 3.24-2 5a6 6 0 1 0 12 0c0-1.532-1.056-3.94-2-5c-1.786 3-2.791 3-4 2z" /> d="M12 12c2-2.96 0-7-1-8c0 3.038-1.773 4.741-3 6c-1.226 1.26-2 3.24-2 5a6 6 0 1 0 12 0c0-1.532-1.056-3.94-2-5c-1.786 3-2.791 3-4 2z" />
</svg> </svg>
</span> </span>
</div> </button>
</nuxt-link> </nuxt-link>
</div> </div>
<div class="overflow-y-scroll my-2 flex gap-y-2 flex-col"> <div class="overflow-y-scroll my-2 flex gap-y-2 flex-col">
<div class="w-full flex justify-center">
<hr class="border-2 rounded-md border-[var(--primary-300)] w-8/12 my-0.5" />
</div>
<nuxt-link v-for="server in servers" <nuxt-link v-for="server in servers"
:to="'/channel/' + server.channels[0]?.id"> :to="'/channel/' + server.channels[0]?.id">
<div :key="server.id" <button :key="server.id"
class="bg-zinc-600/80 p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-zinc-500/60 duration-300 h-[56px] w-[56px]"> class="bg-[var(--primary-300)] p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-[var(--primary-200)] duration-300 h-[56px] w-[56px]">
<svg width="32" <svg width="32"
height="32" height="32"
viewBox="0 0 256 154"> viewBox="0 0 256 154">
@@ -55,12 +58,12 @@
<path fill="url(#svgIDa)" <path fill="url(#svgIDa)"
d="M128 0C93.867 0 72.533 17.067 64 51.2C76.8 34.133 91.733 27.733 108.8 32c9.737 2.434 16.697 9.499 24.401 17.318C145.751 62.057 160.275 76.8 192 76.8c34.133 0 55.467-17.067 64-51.2c-12.8 17.067-27.733 23.467-44.8 19.2c-9.737-2.434-16.697-9.499-24.401-17.318C174.249 14.743 159.725 0 128 0ZM64 76.8C29.867 76.8 8.533 93.867 0 128c12.8-17.067 27.733-23.467 44.8-19.2c9.737 2.434 16.697 9.499 24.401 17.318C81.751 138.857 96.275 153.6 128 153.6c34.133 0 55.467-17.067 64-51.2c-12.8 17.067-27.733 23.467-44.8 19.2c-9.737-2.434-16.697-9.499-24.401-17.318C110.249 91.543 95.725 76.8 64 76.8Z" /> d="M128 0C93.867 0 72.533 17.067 64 51.2C76.8 34.133 91.733 27.733 108.8 32c9.737 2.434 16.697 9.499 24.401 17.318C145.751 62.057 160.275 76.8 192 76.8c34.133 0 55.467-17.067 64-51.2c-12.8 17.067-27.733 23.467-44.8 19.2c-9.737-2.434-16.697-9.499-24.401-17.318C174.249 14.743 159.725 0 128 0ZM64 76.8C29.867 76.8 8.533 93.867 0 128c12.8-17.067 27.733-23.467 44.8-19.2c9.737 2.434 16.697 9.499 24.401 17.318C81.751 138.857 96.275 153.6 128 153.6c34.133 0 55.467-17.067 64-51.2c-12.8 17.067-27.733 23.467-44.8 19.2c-9.737-2.434-16.697-9.499-24.401-17.318C110.249 91.543 95.725 76.8 64 76.8Z" />
</svg> </svg>
</div> </button>
</nuxt-link> </nuxt-link>
</div> </div>
<div> <div>
<div @click="createServerModelOpen = true" <button @click="createServerModelOpen = true"
class="bg-zinc-600/80 p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-zinc-500/60 duration-300"> class="bg-[var(--primary-300)] p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-[var(--primary-200)] duration-300 text-[var(--primary-accent)] cursor-pointer">
<svg width="32" <svg width="32"
height="32" height="32"
viewBox="0 0 24 24"> viewBox="0 0 24 24">
@@ -71,14 +74,14 @@
stroke-width="2" stroke-width="2"
d="M12 5v14m-7-7h14" /> d="M12 5v14m-7-7h14" />
</svg> </svg>
</div> </button>
</div> </div>
</nav> </nav>
<div v-if="createServerModelOpen" <div v-if="createServerModelOpen"
class="absolute z-10 top-0 bottom-0 left-0 right-0"> class="absolute z-10 top-0 bottom-0 left-0 right-0">
<div <div
class="p-4 z-20 absolute bg-zinc-800 shadow-md rounded-md -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 text-white"> class="p-4 z-20 absolute bg-[var(--primary-600)] shadow-md rounded-md -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 text-white">
<h2 class="font-semibold text-xl"> <h2 class="font-semibold text-xl">
Create a server: Create a server:
</h2> </h2>

View File

@@ -1,162 +1,203 @@
<template> <template>
<aside <aside class="bg-[var(--background-color)] min-w-60 w-60 h-screen shadow-sm text-white select-none relative z-[2]">
class="bg-[hsl(223,calc(1*6.9%),19.8%)] min-w-60 w-60 h-screen shadow-sm text-white select-none grid grid-rows-[93.5%_1fr] relative z-[2]"> <div v-if="serverType === 'dms' || !server"
<div v-if="serverType === 'dms' || !server"> class="h-full grid grid-rows-[48px_1fr] w-full">
<div> <section>
<nuxt-link v-for="dm in dms" <h4 @click="serverDropdownOpen = !serverDropdownOpen"
:to="'/channel/@me/' + dm.id"> class="py-3 px-4 font-semibold grid gap-1 grid-cols-[1fr_28px] w-full items-center cursor-pointer p-1 bg-inherit transition-all">
<div <span>Direct messages</span>
class="mx-2 my-4 hover:bg-[hsl(223,calc(1*6.9%),25.8%)] px-2 py-2 w-[calc(240px-1rem)] max-h-10 h-10 overflow-ellipsis rounded-md transition-colors"> </h4>
{{ dm.dmParticipants?.find((e) => e.id !== user.id)?.username }} </section>
</div>
</nuxt-link>
</div>
</div>
<div class="w-full"
v-else>
<h4 @click="serverDropdownOpen = !serverDropdownOpen"
class="py-3 px-4 font-semibold grid gap-1 grid-cols-[1fr_28px] w-full items-center cursor-pointer p-1 bg-[hsl(223,calc(1*6.9%),19.8%)] transition-all"
:class="(!serverDropdownOpen) ? 'hover:bg-[hsl(223,calc(1*6.9%),26.4%)]' : 'bg-[hsl(223,calc(1*6.9%),26.4%)]'">
<span>{{ server.name }}</span>
<button>
<span v-if="!serverDropdownOpen"
class="h-fit w-[20px]">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m6 9l6 6l6-6" />
</svg>
</span>
<span class="h-fit w-[20px]"
v-else>
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 6L6 18M6 6l12 12" />
</svg>
</span>
</button>
</h4>
<div>
<DropdownMenu :opened="serverDropdownOpen">
<div>
<ul class="flex flex-col gap-y-1">
<DropdownItem v-if="userIsOwner || userIsAdmin"
@click="createInvite">
<span>
Invite a friend
</span>
<span class="h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<circle cx="9"
cy="7"
r="4" />
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2m1-10h6m-3-3v6" />
</g>
</svg>
</span>
</DropdownItem>
</ul>
</div>
</DropdownMenu>
</div>
<div class="flex gap-y-1.5 px-1.5 mt-2 flex-col">
<button
class="flex text-center hover:bg-[hsl(223,calc(1*6.9%),26.4%)] px-2 py-1.5 w-full transition-colors rounded drop-shadow-sm gap-1/5 cursor-pointer items-center"
v-for="channel in server.channels"
@click="openChannel(channel.id)"
:key="channel.id">
<span class="h-fit">
<svg class="text-zinc-300/80 my-auto"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="currentColor"
d="m5.41 21l.71-4h-4l.35-2h4l1.06-6h-4l.35-2h4l.71-4h2l-.71 4h6l.71-4h2l-.71 4h4l-.35 2h-4l-1.06 6h4l-.35 2h-4l-.71 4h-2l.71-4h-6l-.71 4h-2M9.53 9l-1.06 6h6l1.06-6h-6Z" />
</svg>
</span>
<span>{{ channel.name }}</span>
</button>
<button v-if="userIsOwner || userIsAdmin"
@click="openCreateChannelModel"
class="flex text-center hover:bg-[hsl(223,calc(1*6.9%),26.4%)] px-2 py-1.5 w-full transition-colors rounded drop-shadow-sm cursor-pointer items-center">
<span>
<svg class="text-zinc-300/80 my-auto"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 5v14m-7-7h14" />
</svg>
</span>
<span>Add channel</span>
</button>
</div>
</div>
<div class="relative"> <div
<DropdownMenu class="bottom-full" class="h-[calc(100%-24px)] my-3 mx-1 grid grid-rows-[1fr_56px] bg-[var(--foreground-color)] rounded-lg">
:inverted="true" <div class="h-fit">
:opened="userDropdownOpen"> <nuxt-link v-for="dm in dms"
:to="'/channel/@me/' + dm.id">
<div
class="mx-2 my-4 bg-inherit hover:backdrop-brightness-[1.35] px-2 py-2 max-h-10 h-10 overflow-ellipsis rounded-md transition-all">
{{ dm.dmParticipants?.find((e) => e.id !== user.id)?.username }}
</div>
</nuxt-link>
</div>
</div>
</div>
<div v-else
class="w-full h-full max-h-screen grid grid-rows-[48px_1fr]">
<section>
<h4 @click="serverDropdownOpen = !serverDropdownOpen"
class="py-3 px-4 font-semibold grid gap-1 grid-cols-[1fr_28px] w-full items-center cursor-pointer p-1 bg-inherit transition-all rounded-lg"
:class="(!serverDropdownOpen) ? 'hover:backdrop-brightness-125' : 'backdrop-brightness-125'">
<span>{{ server.name }}</span>
<button>
<span v-if="!serverDropdownOpen"
class="h-fit w-[20px]">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m6 9l6 6l6-6" />
</svg>
</span>
<span class="h-fit w-[20px]"
v-else>
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 6L6 18M6 6l12 12" />
</svg>
</span>
</button>
</h4>
<div> <div>
<ul class="flex flex-col gap-y-1"> <DropdownMenu :opened="serverDropdownOpen">
<DropdownItem v-if="userIsOwner || userIsAdmin" <div>
@click="createInvite"> <ul class="flex flex-col gap-y-1">
<span> <DropdownItem v-if="userIsOwner || userIsAdmin"
Invite a friend @click="createInvite">
</span> <span>
<span class="mr-1.5 h-fit"> Invite a friend
</span>
<span class="h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<circle cx="9"
cy="7"
r="4" />
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2m1-10h6m-3-3v6" />
</g>
</svg>
</span>
</DropdownItem>
</ul>
</div>
</DropdownMenu>
</div>
</section>
<div
class="h-[calc(100%-24px)] my-3 mx-1 grid grid-rows-[1fr_56px] bg-[var(--foreground-color)] rounded-lg">
<div class="flex gap-y-1.5 px-1.5 mt-2 flex-col overflow-x-scroll">
<button
class="flex text-center bg-inherit hover:backdrop-brightness-[1.35] px-2 py-1.5 w-full transition-all rounded drop-shadow-sm gap-1/5 cursor-pointer items-center"
v-for="channel in server.channels"
@click="openChannel(channel.id)"
:key="channel.id">
<span class="h-fit">
<svg class="text-zinc-300/80 my-auto"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="currentColor"
d="m5.41 21l.71-4h-4l.35-2h4l1.06-6h-4l.35-2h4l.71-4h2l-.71 4h6l.71-4h2l-.71 4h4l-.35 2h-4l-1.06 6h4l-.35 2h-4l-.71 4h-2l.71-4h-6l-.71 4h-2M9.53 9l-1.06 6h6l1.06-6h-6Z" />
</svg>
</span>
<span>{{ channel.name }}</span>
</button>
<button v-if="userIsOwner || userIsAdmin"
@click="openCreateChannelModel"
class="flex text-center bg-inherit hover:backdrop-brightness-[1.45] px-2 py-1.5 w-full transition-all rounded drop-shadow-sm cursor-pointer items-center">
<span>
<svg class="text-zinc-300/80 my-auto"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 5v14m-7-7h14" />
</svg>
</span>
<span>Add channel</span>
</button>
</div>
<div class="relative bottom-0">
<DropdownMenu class="bottom-full"
:inverted="true"
:opened="userDropdownOpen">
<div>
<ul class="flex flex-col gap-y-1">
<DropdownItem v-if="userIsOwner || userIsAdmin"
@click="createInvite">
<span>
Invite a friend
</span>
<span class="mr-1.5 h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<circle cx="9"
cy="7"
r="4" />
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2m1-10h6m-3-3v6" />
</g>
</svg>
</span>
</DropdownItem>
<DropdownItem @click="logout"
danger="true">
<span>
Logout
</span>
<span class="mr-1.5 h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<path
d="M14 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-2" />
<path d="M7 12h14l-3-3m0 6l3-3" />
</g>
</svg>
</span>
</DropdownItem>
</ul>
</div>
</DropdownMenu>
<div class="h-full p-3">
<div class="grid grid-cols-[32px_1fr_32px] gap-x-2 items-center">
<span class="bg-[hsl(220,calc(1*6.8%),22.6%)] w-[32px] h-[32px] rounded-full"></span>
<span class="h-fit w-fit overflow-ellipsis">{{ user.username }}</span>
<button @click="userDropdownOpen = !userDropdownOpen"
class="text-zinc-300 hover:backdrop-brightness-90 p-1 rounded-md transition-all">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width="20" width="24"
height="20" height="24"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<circle cx="9"
cy="7"
r="4" />
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2m1-10h6m-3-3v6" />
</g>
</svg>
</span>
</DropdownItem>
<DropdownItem @click="logout" danger="true">
<span>
Logout
</span>
<span class="mr-1.5 h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"> viewBox="0 0 24 24">
<g fill="none" <g fill="none"
stroke="currentColor" stroke="currentColor"
@@ -164,38 +205,15 @@
stroke-linejoin="round" stroke-linejoin="round"
stroke-width="2"> stroke-width="2">
<path <path
d="M14 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-2" /> d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 0 0-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 0 0-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 0 0-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.94-1.543.826-3.31 2.37-2.37c1 .608 2.296.07 2.572-1.065z" />
<path d="M7 12h14l-3-3m0 6l3-3" /> <circle cx="12"
cy="12"
r="3" />
</g> </g>
</svg> </svg>
</span> </button>
</DropdownItem> </div>
</ul> </div>
</div>
</DropdownMenu>
<div class="bg-[hsl(220,calc(1*6.8%),17.3%)] h-full p-3">
<div class="grid grid-cols-[32px_1fr_32px] gap-x-2 items-center">
<span class="bg-[hsl(220,calc(1*6.8%),22.6%)] w-[32px] h-[32px] rounded-full"></span>
<span class="h-fit w-fit overflow-ellipsis">{{ user.username }}</span>
<button @click="userDropdownOpen = !userDropdownOpen"
class="text-zinc-300 hover:bg-[hsl(220,calc(1*6.8%),14.3%)] p-1 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<path
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 0 0-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 0 0-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 0 0-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.94-1.543.826-3.31 2.37-2.37c1 .608 2.296.07 2.572-1.065z" />
<circle cx="12"
cy="12"
r="3" />
</g>
</svg>
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -203,7 +221,7 @@
<div v-if="createChannelModelOpen" <div v-if="createChannelModelOpen"
class="absolute z-10 top-0 bottom-0 left-0 right-0"> class="absolute z-10 top-0 bottom-0 left-0 right-0">
<div class="bg-zinc-900/80 w-screen h-screen" <div class="bg-[var(--primary-600)] w-screen h-screen"
@click="createChannelModelOpen = false"> @click="createChannelModelOpen = false">
</div> </div>
<div <div

View File

@@ -21,13 +21,12 @@ export default {
useGlobalStore().addDM(server); useGlobalStore().addDM(server);
if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumably?') if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumably?')
useGlobalStore().setActiveServer('dms', route.params.id); useGlobalStore().setActiveServer('dms', route.params.id);
useGlobalStore().setActiveChannel(server)
server.messages?.forEach((e) => { server.messages?.forEach((e) => {
e.body = parseMessageBody(e.body, useGlobalStore().activeChannel) e.body = parseMessageBody(e.body, useGlobalStore().activeChannel)
}) })
useGlobalStore().setActiveChannel(server)
return { return {
server server
} }

View File

@@ -48,13 +48,12 @@ export default {
useGlobalStore().addServer(realServer); useGlobalStore().addServer(realServer);
if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumiably?') if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumiably?')
useGlobalStore().setActiveServer('servers', route.params.id) useGlobalStore().setActiveServer('servers', route.params.id)
useGlobalStore().setActiveChannel(server)
server.messages?.forEach((e) => { server.messages?.forEach((e) => {
e.body = parseMessageBody(e.body, useGlobalStore().activeChannel) e.body = parseMessageBody(e.body, useGlobalStore().activeChannel)
}) })
useGlobalStore().setActiveChannel(server)
return { return {
server, server,
} }

View File

@@ -6,12 +6,12 @@
<form class="flex flex-col gap-y-2 my-2" <form class="flex flex-col gap-y-2 my-2"
@submit.prevent="signup()"> @submit.prevent="signup()">
<input <input
class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[hsl(218,calc(1*4.6%),46.9%)] focus:outline-none" class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[var(--primary-placeholder)] focus:outline-none"
name="username" name="username"
v-model="username" v-model="username"
placeholder="username" /> placeholder="username" />
<input <input
class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[hsl(218,calc(1*4.6%),46.9%)] focus:outline-none" class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[var(--primary-placeholder)] focus:outline-none"
name="password" name="password"
type="password" type="password"
v-model="password" v-model="password"

View File

@@ -6,22 +6,23 @@
<form class="flex flex-col gap-y-2 my-2" <form class="flex flex-col gap-y-2 my-2"
@submit.prevent="signup()"> @submit.prevent="signup()">
<input <input
class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[hsl(218,calc(1*4.6%),46.9%)] focus:outline-none" class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[var(--primary-placeholder)] focus:outline-none"
name="username" name="username"
v-model="username" v-model="username"
placeholder="username" /> placeholder="username" />
<input <input
class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[hsl(218,calc(1*4.6%),46.9%)] focus:outline-none" class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[var(--primary-placeholder)] focus:outline-none"
name="email" name="email"
v-model="email" v-model="email"
placeholder="email" /> placeholder="email" />
<input <input
class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[hsl(218,calc(1*4.6%),46.9%)] focus:outline-none" class="border border-[hsl(218,calc(1*7.9%),23.7%)] px-4 py-2 rounded w-full bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[var(--primary-placeholder)] focus:outline-none"
name="password" name="password"
type="password" type="password"
v-model="password" v-model="password"
placeholder="password" /> placeholder="password" />
<input type="submit" class="w-full bg-[#5865F2] py-2 px-4 rounded cursor-pointer" /> <input type="submit"
class="w-full bg-[#5865F2] py-2 px-4 rounded cursor-pointer" />
</form> </form>
<div class="text-center">Or <nuxt-link class="hover:underline text-blue-500" <div class="text-center">Or <nuxt-link class="hover:underline text-blue-500"
to="/login">Login</nuxt-link></div> to="/login">Login</nuxt-link></div>

View File

@@ -0,0 +1,52 @@
import { IChannel, IServer, SafeUser } from '~/types'
import emojiRegex from 'emoji-regex'
import { PrismaClient } from '@prisma/client'
import parseBody from '~~/utils/parseMessageBody'
const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) {
event.node.res.statusCode = 401;
return {
message: 'You must be logged in to send a message.'
}
}
const { id: channelId, messageId } = event.context.params
const message = await prisma.message.findFirst({
where: {
id: messageId,
channelId: channelId,
},
include: {
creator: true
}
})
if (!message) {
event.node.res.statusCode = 404;
return {
message: `message in channel ${channelId} with id ${messageId} is not found.`
}
}
if (event.context.user.id !== message.creator.id) {
event.node.res.statusCode = 401;
return {
message: 'you are not allowed to delete that message.'
}
}
await prisma.message.delete({
where: {
id: message.id
}
})
global.io.emit(`message-${event.context.params.id}`, { message: { id: message.id }, deleted: true });
return {
message: 'message successfully deleted.'
}
})

View File

@@ -85,6 +85,10 @@ export const useGlobalStore = defineStore('global', {
if (messageIndex < 0) return; if (messageIndex < 0) return;
this.activeChannel.messages[messageIndex] = message this.activeChannel.messages[messageIndex] = message
}, },
removeMessage(messageId: string) {
if (!this.activeChannel.messages.find(m => m.id === messageId)) return;
this.activeChannel.messages = this.activeChannel.messages.filter(m => m.id !== messageId)
},
logout() { logout() {
this.dms = [] this.dms = []
this.servers = [] this.servers = []

View File

@@ -9,7 +9,14 @@ module.exports = {
'./nuxt.config.{js,ts}', './nuxt.config.{js,ts}',
], ],
theme: { theme: {
extend: {}, extend: {
colors: {
'primary': {
'dark-bg': '#181624',
DEFAULT: '#282a36'
}
}
},
}, },
plugins: [], plugins: [],
} }

View File

@@ -1,6 +1,7 @@
import { IChannel } from "~/types"; import { IChannel } from "~/types";
export default function parseBody(body: string, activeChannel: IChannel) { export default function parseBody(body: string, activeChannel: IChannel) {
if (!activeChannel.id) throw new Error("No active channel")
body = escape(body); body = escape(body);
const rules = [ const rules = [
//bold, italics and paragragh rules //bold, italics and paragragh rules
@@ -18,14 +19,14 @@ export default function parseBody(body: string, activeChannel: IChannel) {
body = body.replace(rule, template); body = body.replace(rule, template);
}) })
const mentions = body.match(/<@([a-z]|[0-9]){25}>/g); const mentions = body.match(/&#60;&#64;([a-z]|[0-9]){25}&#62;/g);
if (mentions) { if (mentions) {
const participants = (activeChannel.DM) ? activeChannel.dmParticipants : activeChannel.server.participants; const participants = (activeChannel.DM) ? activeChannel.dmParticipants : activeChannel.server.participants;
if (!participants) throw new Error(`participants in channel "${activeChannel.id}" not found"`) if (!participants) throw new Error(`participants in channel "${activeChannel.id}" not found"`)
mentions.forEach((e: string) => { mentions.forEach((e: string) => {
if (!e) return if (!e) return
const id = e.split('<@')[1]?.split('>')[0]; const id = e.split('&#60;&#64;')[1]?.split('&#62;')[0];
if (!id) return; if (!id) return;
const user = participants.find((e) => e.id === id) const user = participants.find((e) => e.id === id)
if (!user) return; if (!user) return;