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 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>
<li>
<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="(danger) ? 'hover:bg-[hsl(359,66.7%,54.1%)]' : ''">
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-[var(--primary-danger)]' : ''">
<slot />
</button>
</li>

View File

@@ -1,6 +1,6 @@
<template>
<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'"
v-if="opened">
<slot />

View File

@@ -42,11 +42,11 @@ export default {
<template>
<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="flex-row gap-x-2 overflow-x-scroll">
<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"
width="24"
height="24"
@@ -62,7 +62,7 @@ export default {
</svg>
</button>
<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"
width="24"
height="24"
@@ -76,7 +76,7 @@ export default {
</svg>
</button>
<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"
width="24"
height="24"
@@ -90,7 +90,7 @@ export default {
</svg>
</button>
<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"
width="24"
height="24"
@@ -107,7 +107,7 @@ export default {
</svg>
</button>
<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"
width="24"
height="24"
@@ -121,7 +121,7 @@ export default {
</svg>
</button>
<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"
width="24"
height="24"
@@ -139,7 +139,7 @@ export default {
</svg>
</button>
<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"
width="24"
height="24"
@@ -157,7 +157,7 @@ export default {
</svg>
</button>
<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"
width="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"
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">
<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
}}</h6>
<div class="flex flex-wrap"
:id="category.name">
<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)"
:aria-label='emoji.name.toLowerCase()'>
<span :style="emojiStyles(emoji.short_name, 32)"

View File

@@ -1,11 +1,11 @@
<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
server</p>
<span class="text-xl font-bold capitalize leading-loose">{{ invite.server.name }}</span>
<div class="flex items-center">
<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>
</div>
<div class="flex w-full justify-end">

View File

@@ -1,14 +1,18 @@
<template>
<div class="absolute right-0 mr-10 -top-[20px] h-fit opacity-0 pointer-events-none action-buttons z-10"
:class="(emojiPickerOpen) ? 'opacity-100 pointer-events-auto' : ''">
<div class="absolute right-[38px] top-0 w-[375px]">
<EmojiPicker v-on:pickedEmoji="pickedEmoji($event)"
:opened="emojiPickerOpen" />
</div>
<div class="relative">
<div @click="emojiPickerOpen = !emojiPickerOpen"
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">
<button class="p-1">
<div class="relative message-wrapper"
@mouseenter="mouseEnter()"
@mouseleave="mouseLeave()">
<div class="absolute right-0 mr-10 -top-[20px] h-fit opacity-0 pointer-events-none action-buttons z-[5]"
:class="(emojiPickerOpen) ? 'opacity-100 pointer-events-auto' : ''">
<div class="absolute top-0 w-[375px]"
:style="emojiPickerStyles">
<EmojiPicker v-on:pickedEmoji="pickedEmoji($event)"
:opened="emojiPickerOpen" />
</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"
width="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" />
</svg>
</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 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">
<div class="message-content">
<div class="message-sender-text">
<p class="mb-1 font-semibold w-fit"
v-if="showUsername">
{{ message.creator.username }}
</p>
<p class="break-words max-w-full" v-html="message.body"></p>
</div>
<div v-for="invite in message.invites">
<InviteCard :invite="invite" />
</div>
<div class="flex gap-2 flex-wrap">
<button @click="toggleReaction(reaction.emoji.name)"
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="(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">
<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 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]"
:class="classes">
<div class="message-content">
<div class="message-sender-text">
<p class="mb-1 font-semibold w-fit"
v-if="showUsername">
{{ message.creator.username }}
</p>
<p class="break-words max-w-full"
v-html="message.body"></p>
</div>
<div v-for="invite in message.invites">
<InviteCard :invite="invite" />
</div>
<div class="flex gap-2 flex-wrap">
<button @click="toggleReaction(reaction.emoji.name)"
v-for="reaction in message.reactions"
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)]' : ''">
<div class="flex items-center mr-0.5 w-6 drop-shadow">
<span :style="emojiStyles(reaction.emoji.name, 16)"></span>
</div>
</div>
</button>
<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>
</button>
</div>
</div>
</div>
</div>
@@ -61,6 +120,7 @@
import { PropType } from 'vue';
import { IMessage } from '~/types';
import { useGlobalStore } from '~/stores/store';
import { useClipboard } from '@vueuse/core'
import emojiJson from '~/assets/json/emoji.json';
export default {
@@ -82,6 +142,15 @@ export default {
return {
user: storeToRefs(useGlobalStore()).user,
emojiPickerOpen: false,
emojiPickerStyles: this.calculateEmojiPickerRight(),
actionButtonOverflowMenuOpen: false,
}
},
setup() {
const { text, copy, copied, isSupported } = useClipboard()
return {
copy
}
},
methods: {
@@ -96,7 +165,7 @@ export default {
emojiStyles(emoji: string, width: number) {
const emojis = emojiJson.filter((e) => e.has_img_twitter)
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 {};
}
const sheet_x = (twemoji.sheet_y * (32 + 2)) / 2;
@@ -117,6 +186,37 @@ export default {
this.toggleReaction(replacementEmoji.emoji)
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>
@@ -128,8 +228,8 @@ export default {
}
pre.codeblock {
background-color: hsl(223, 6.9%, 19.8%);
border: 1px solid hsl(216, 7.2%, 13.5%);
background-color: var(--primary-500);
border: 1px solid var(--primary-700);
border-radius: 0.375rem;
white-space: prewrap;
margin-top: 4px;
@@ -147,10 +247,9 @@ pre.codeblock code {
}
code.inline-code {
background-color: hsl(223, 6.9%, 19.8%);
background-color: var(--primary-500);
padding: 0.2rem;
font-size: 85%;
border-radius: 4px;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="h-full bg-[hsl(220,calc(1*7.7%),22.9%)] relative text-white">
<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="h-full relative text-white bg-[var(--background-color)] grid grid-rows-[48px_1fr]">
<div class="w-full px-4 py-3 z-[1]">
<div v-if="!server.DM"
class="flex items-center">
<span class="mr-1">
@@ -40,77 +40,82 @@
}}</span>
</div>
</div>
<div class="w-full h-[calc(100%-76px-48px)] top-[48px] absolute overflow-y-scroll pb-1"
id="conversation-pane">
<div>
<div v-if="server.messages.length === 0">
<p>No messages yet</p>
<section
class="bg-[var(--foreground-color)] my-3 mx-1 h-[calc(100%-24px)] overflow-hidden rounded-lg relative grid grid-rows-[1fr_70px]">
<div class="h-full overflow-y-scroll" id="conversation-pane">
<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 v-else
v-for="(message, i) in server.messages" class="relative message-wrapper">
<Message :message="message"
:classes="calculateMessageClasses(message, i)"
:showUsername="i === 0 || server.messages[i - 1]?.creator.id !== message.creator.id" />
<div v-if="showSearch"
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">
<div class="relative flex flex-col">
<div v-for="user in searchResults"
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 v-if="showSearch"
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">
<div class="relative flex flex-col">
<div v-for="user in searchResults"
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"
@click="completeMention(user)">
{{ user.username }}
</div>
</div>
</div>
<div class="conversation-input w-[calc(100vw-88px-240px)] h-fit">
<form @keyup="checkForMentions"
@keypress="typing($event)"
@submit.prevent="sendMessage"
@keydown.enter.exact.prevent="sendMessage"
class="relative px-4 w-full pt-1.5 h-fit pb-1">
<div id="textbox"
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">
<textarea type="text"
id="messageBox"
class="bg-transparent focus:outline-none py-2 w-full resize-none leading-relaxed h-[44px]"
cols="1"
v-model="messageContent"
placeholder="Send a Message..." />
<input type="submit"
class="absolute -top-full -left-full invisible"
id="submit">
<label for="submit"
class="py-1 px-1.5 h-fit my-auto cursor-pointer"
role="button"><svg width="32"
height="32"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
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" />
</svg></label>
</div>
<div class="w-full h-4">
<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>
<div class="flex absolute flex-row bottom-0 w-full h-fit bg-inherit mb-1">
<form @keyup="checkForMentions"
@keypress="typing($event)"
@submit.prevent="sendMessage"
@keydown.enter.exact.prevent="sendMessage"
class="relative px-4 w-full pt-1.5 h-fit pb-1">
<div id="textbox"
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">
<textarea type="text"
id="messageBox"
class="bg-transparent focus:outline-none py-2 w-full resize-none leading-relaxed h-[44px]"
cols="1"
v-model="messageContent"
placeholder="Send a Message..." />
<input type="submit"
class="absolute -top-full -left-full invisible"
id="submit">
<label for="submit"
class="py-1 px-1.5 h-fit my-auto cursor-pointer"
role="button"><svg width="32"
height="26"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
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" />
</svg></label>
</div>
<div class="w-full h-4">
<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>
is typing
</span>
is typing
</span>
<span v-else>Several users are typing</span>
</p>
</div>
</form>
</div>
<span v-else>Several users are typing</span>
</p>
</div>
</form>
</div>
</section>
</div>
</template>
@@ -274,8 +279,13 @@ export default {
listenToWebsocket(conversationDiv: HTMLElement) {
this.socket.removeAllListeners();
this.socket.on(`message-${this.server.id}`, (ev: { message: IMessage }) => {
let { message } = ev
this.socket.on(`message-${this.server.id}`, (ev: { message: IMessage, deleted?: boolean }) => {
let { message, deleted } = ev
if (deleted) {
useGlobalStore().removeMessage(message.id)
return;
}
message.body = parseMessageBody(message.body, useGlobalStore().activeChannel)
@@ -329,16 +339,4 @@ export default {
// }
},
}
</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>
</script>

View File

@@ -1,10 +1,10 @@
<template>
<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>
<nuxt-link to="/channel/@me">
<div
class="bg-zinc-600/80 p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-zinc-500/60 duration-300">
<button
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>
<svg width="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" />
</svg>
</span>
</div>
</button>
</nuxt-link>
</div>
<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"
:to="'/channel/' + server.channels[0]?.id">
<div :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]">
<button :key="server.id"
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"
height="32"
viewBox="0 0 256 154">
@@ -55,12 +58,12 @@
<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" />
</svg>
</div>
</button>
</nuxt-link>
</div>
<div>
<div @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">
<button @click="createServerModelOpen = true"
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"
height="32"
viewBox="0 0 24 24">
@@ -71,14 +74,14 @@
stroke-width="2"
d="M12 5v14m-7-7h14" />
</svg>
</div>
</button>
</div>
</nav>
<div v-if="createServerModelOpen"
class="absolute z-10 top-0 bottom-0 left-0 right-0">
<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">
Create a server:
</h2>

View File

@@ -1,162 +1,203 @@
<template>
<aside
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>
<nuxt-link v-for="dm in dms"
:to="'/channel/@me/' + dm.id">
<div
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">
{{ dm.dmParticipants?.find((e) => e.id !== user.id)?.username }}
</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>
<aside class="bg-[var(--background-color)] min-w-60 w-60 h-screen shadow-sm text-white select-none relative z-[2]">
<div v-if="serverType === 'dms' || !server"
class="h-full grid grid-rows-[48px_1fr] w-full">
<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">
<span>Direct messages</span>
</h4>
</section>
<div class="relative">
<DropdownMenu class="bottom-full"
:inverted="true"
:opened="userDropdownOpen">
<div
class="h-[calc(100%-24px)] my-3 mx-1 grid grid-rows-[1fr_56px] bg-[var(--foreground-color)] rounded-lg">
<div class="h-fit">
<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>
<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">
<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>
</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"
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"
width="24"
height="24"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
@@ -164,38 +205,15 @@
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" />
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>
</span>
</DropdownItem>
</ul>
</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>
</button>
</div>
</div>
</div>
</div>
</div>
@@ -203,7 +221,7 @@
<div v-if="createChannelModelOpen"
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">
</div>
<div

View File

@@ -21,13 +21,12 @@ export default {
useGlobalStore().addDM(server);
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().setActiveChannel(server)
server.messages?.forEach((e) => {
e.body = parseMessageBody(e.body, useGlobalStore().activeChannel)
})
useGlobalStore().setActiveChannel(server)
return {
server
}

View File

@@ -48,13 +48,12 @@ export default {
useGlobalStore().addServer(realServer);
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().setActiveChannel(server)
server.messages?.forEach((e) => {
e.body = parseMessageBody(e.body, useGlobalStore().activeChannel)
})
useGlobalStore().setActiveChannel(server)
return {
server,
}

View File

@@ -6,12 +6,12 @@
<form class="flex flex-col gap-y-2 my-2"
@submit.prevent="signup()">
<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"
v-model="username"
placeholder="username" />
<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"
type="password"
v-model="password"

View File

@@ -6,22 +6,23 @@
<form class="flex flex-col gap-y-2 my-2"
@submit.prevent="signup()">
<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"
v-model="username"
placeholder="username" />
<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"
v-model="email"
placeholder="email" />
<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"
type="password"
v-model="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>
<div class="text-center">Or <nuxt-link class="hover:underline text-blue-500"
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;
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() {
this.dms = []
this.servers = []

View File

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

View File

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