From 5b1d25807e9b9462b3b5eea270c3a912f4a6a976 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 1 Jun 2025 16:11:50 +0200 Subject: [PATCH 1/5] feat: fix messages not grouping correctly and implement scrolling for loading previous messages --- components/Message.vue | 12 ++-- components/MessageArea.vue | 125 ++++++++++++++++++++++++++----------- 2 files changed, 94 insertions(+), 43 deletions(-) diff --git a/components/Message.vue b/components/Message.vue index a6d95ac..592a995 100644 --- a/components/Message.vue +++ b/components/Message.vue @@ -55,10 +55,6 @@ const dateHidden = ref(true); const date = new Date(props.timestamp); -console.log("Message.vue: message:", props.text); -console.log("Message.vue: message type:", props.type); -console.log("Message.vue: is last?", props.last); - let dateHour = date.getHours(); let dateMinute = date.getMinutes(); if (props.format == "12") { @@ -75,7 +71,12 @@ if (props.format == "12") { messageDate.value = `${dateHour}:${dateMinute < 10 ? "0" + dateMinute : dateMinute}` } -onMounted(() => { +console.log("message:", props.text); +console.log("author:", props.username); + +onMounted(async () => { + console.log("adding listeners") + await nextTick(); messageElement.value?.addEventListener("mouseenter", (e: Event) => { dateHidden.value = false; }); @@ -83,6 +84,7 @@ onMounted(() => { messageElement.value?.addEventListener("mouseleave", (e: Event) => { dateHidden.value = true; }); + console.log("added listeners"); }); //function toggleTooltip(e: Event) { diff --git a/components/MessageArea.vue b/components/MessageArea.vue index ff523cf..2f6aa97 100644 --- a/components/MessageArea.vue +++ b/components/MessageArea.vue @@ -4,7 +4,7 @@
@@ -34,50 +34,66 @@ const messagesRes: MessageResponse[] | undefined = await fetchWithApi( { query: { "amount": props.amount ?? 100, "offset": props.offset ?? 0 } } ); -function groupMessage(message: MessageResponse) { - messageTimestamps.value[message.uuid] = uuidToTimestamp(message.uuid); +const firstMessageByUsers = ref>({}); +const previousMessage = ref(); - console.log("message:", message.message); - const firstByUser = firstMessageByUsers.value[message.user.uuid]; - if (firstByUser) { - console.log("first by user exists"); - if (message.user.uuid != firstByUser.user.uuid) { - console.log("message is by new user, setting their first message") - firstMessageByUsers.value[message.user.uuid] = message; - console.log("RETURNING FALSE"); - messagesType.value[message.uuid] = "normal"; - return; - } - } else { - console.log("first by user doesn't exist"); - console.log(`setting first post by user ${message.user.username} to "${message.message}" with timestamp ${messageTimestamps.value[message.uuid]}`); +async function groupMessage(message: MessageResponse, options?: { prevMessage?: MessageResponse, reverse?: boolean }) { + await nextTick(); + messageTimestamps.value[message.uuid] = uuidToTimestamp(message.uuid); + console.log("message:", message.message); + console.log("author:", message.user.username, `(${message.user.uuid})`); + + if (!previousMessage.value || previousMessage.value && message.user.uuid != previousMessage.value.user.uuid) { + console.log("no previous message or author is different than last messsage's"); + messagesType.value[message.uuid] = "normal"; + previousMessage.value = message; + console.log("set previous message to:", previousMessage.value.message); + console.log(`setting first post by user ${message.user.username} to "${message.message}" with timestamp ${messageTimestamps.value[message.uuid]}`); + firstMessageByUsers.value[message.user.uuid] = message; + return; + } + + + const firstByUser = firstMessageByUsers.value[message.user.uuid]; + if (firstByUser) { + console.log("first by user exists:", firstByUser); + if (message.user.uuid != firstByUser.user.uuid) { + console.log("message is by new user, setting their first message") firstMessageByUsers.value[message.user.uuid] = message; console.log("RETURNING FALSE"); messagesType.value[message.uuid] = "normal"; return; } - - const prevTimestamp = messageTimestamps.value[firstByUser.uuid]; - const timestamp = messageTimestamps.value[message.uuid]; - console.log("first message timestamp:", prevTimestamp); - console.log("timestamp:", timestamp); - const diff = (timestamp - prevTimestamp); - console.log("min diff:", messageGroupingMaxDifference); - console.log("diff:", diff); - const lessThanMax = diff <= messageGroupingMaxDifference; - console.log("group?", lessThanMax); - if (!lessThanMax) { - console.log("diff exceeds max"); - console.log(`setting first post by user ${message.user.username} to "${message.message}" with timestamp ${messageTimestamps.value[message.uuid]}`) - firstMessageByUsers.value[message.user.uuid] = message; - messagesType.value[message.uuid] = "normal"; - return; - } - console.log("RETURNING " + lessThanMax.toString().toUpperCase()); - messagesType.value[message.uuid] = "grouped"; + } else { + console.log("first by user doesn't exist"); + console.log(`setting first post by user ${message.user.username} to "${message.message}" with timestamp ${messageTimestamps.value[message.uuid]}`); + firstMessageByUsers.value[message.user.uuid] = message; + console.log("RETURNING FALSE"); + messagesType.value[message.uuid] = "normal"; + return; + } + + const prevTimestamp = messageTimestamps.value[firstByUser.uuid]; + const timestamp = messageTimestamps.value[message.uuid]; + + console.log("first message timestamp:", prevTimestamp); + console.log("timestamp:", timestamp); + const diff = Math.abs(timestamp - prevTimestamp); + console.log("min diff:", messageGroupingMaxDifference); + console.log("diff:", diff); + const lessThanMax = diff <= messageGroupingMaxDifference; + console.log("group?", lessThanMax); + if (!lessThanMax) { + console.log("diff exceeds max"); + console.log(`setting first post by user ${message.user.username} to "${message.message}" with timestamp ${messageTimestamps.value[message.uuid]}`) + firstMessageByUsers.value[message.user.uuid] = message; + messagesType.value[message.uuid] = "normal"; + return; + } + console.log("RETURNING " + lessThanMax.toString().toUpperCase()); + messagesType.value[message.uuid] = "grouped"; } -const firstMessageByUsers = ref>({}); if (messagesRes) { messagesRes.reverse(); console.log("messages res:", messagesRes.map(msg => msg.message)); @@ -97,6 +113,7 @@ if (messagesRes) messages.value = messagesRes; const accessToken = useCookie("access_token").value; const apiBase = useCookie("api_base").value; const { refresh } = useAuth(); +const { fetchMessages } = useApi(); let ws: WebSocket; @@ -121,7 +138,7 @@ if (accessToken && apiBase) { console.log("message uuid:", event.data.uuid); const parsedData = JSON.parse(event.data); - groupMessage(parsedData); + await groupMessage(parsedData); console.log("parsed message type:", messagesType.value[parsedData.uuid]); console.log("parsed message timestamp:", messageTimestamps.value[parsedData.uuid]); messages.value.push(parsedData); @@ -147,9 +164,41 @@ function sendMessage(e: Event) { } } +const route = useRoute(); + onMounted(async () => { + if (import.meta.server) return; if (messagesElement.value) { scrollToBottom(messagesElement); + let fetched = false; + let offset = messages.value.length; + messagesElement.value.addEventListener("scroll", async (e) => { + if (e.target) { + const target = e.target as HTMLDivElement; + if (target.scrollTop <= target.scrollHeight * 0.1) { + if (fetched) return; + fetched = true; + console.log("scroll height is at 10% or less"); + //console.log("current oldest:", currentOldestMessage); + const olderMessages = await fetchMessages(route.params.channelId as string, { amount: 2, offset: offset }); + if (olderMessages) { + olderMessages.reverse(); + console.log("older messages:", olderMessages); + if (olderMessages.length == 0) return; + for (const [i, oldMessage] of olderMessages.entries()) { + console.log("old message:", oldMessage); + messages.value.unshift(oldMessage); + for (const message of messages.value) { + await groupMessage(message); + } + } + offset += 2; + } + } else { + fetched = false; + } + } + }); } }); From 2c2013fa814bb4c6e06c0218e4a7f0f90d7356fb Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 1 Jun 2025 16:22:51 +0200 Subject: [PATCH 2/5] feat: update offset for fetching older messages --- components/MessageArea.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/MessageArea.vue b/components/MessageArea.vue index 2f6aa97..22b1e61 100644 --- a/components/MessageArea.vue +++ b/components/MessageArea.vue @@ -171,6 +171,7 @@ onMounted(async () => { if (messagesElement.value) { scrollToBottom(messagesElement); let fetched = false; + const amount = messages.value.length; let offset = messages.value.length; messagesElement.value.addEventListener("scroll", async (e) => { if (e.target) { @@ -180,7 +181,7 @@ onMounted(async () => { fetched = true; console.log("scroll height is at 10% or less"); //console.log("current oldest:", currentOldestMessage); - const olderMessages = await fetchMessages(route.params.channelId as string, { amount: 2, offset: offset }); + const olderMessages = await fetchMessages(route.params.channelId as string, { amount, offset }); if (olderMessages) { olderMessages.reverse(); console.log("older messages:", olderMessages); @@ -192,7 +193,7 @@ onMounted(async () => { await groupMessage(message); } } - offset += 2; + offset += offset; } } else { fetched = false; From 9fee630a6806fbc330ccb852924cd6225f5d7216 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 1 Jun 2025 16:23:42 +0200 Subject: [PATCH 3/5] fix: some messages not getting type correctly due to nextTick() use --- components/MessageArea.vue | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/MessageArea.vue b/components/MessageArea.vue index 22b1e61..5ad027c 100644 --- a/components/MessageArea.vue +++ b/components/MessageArea.vue @@ -37,8 +37,7 @@ const messagesRes: MessageResponse[] | undefined = await fetchWithApi( const firstMessageByUsers = ref>({}); const previousMessage = ref(); -async function groupMessage(message: MessageResponse, options?: { prevMessage?: MessageResponse, reverse?: boolean }) { - await nextTick(); +function groupMessage(message: MessageResponse, options?: { prevMessage?: MessageResponse, reverse?: boolean }) { messageTimestamps.value[message.uuid] = uuidToTimestamp(message.uuid); console.log("message:", message.message); console.log("author:", message.user.username, `(${message.user.uuid})`); @@ -138,7 +137,7 @@ if (accessToken && apiBase) { console.log("message uuid:", event.data.uuid); const parsedData = JSON.parse(event.data); - await groupMessage(parsedData); + groupMessage(parsedData); console.log("parsed message type:", messagesType.value[parsedData.uuid]); console.log("parsed message timestamp:", messageTimestamps.value[parsedData.uuid]); messages.value.push(parsedData); @@ -190,7 +189,7 @@ onMounted(async () => { console.log("old message:", oldMessage); messages.value.unshift(oldMessage); for (const message of messages.value) { - await groupMessage(message); + groupMessage(message); } } offset += offset; From 256889a573e62a45a84eebdbc828c949faef28db Mon Sep 17 00:00:00 2001 From: JustTemmie <47639983+JustTemmie@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:46:09 +0200 Subject: [PATCH 4/5] feat: favicon!! --- composables/auth.ts | 1 + public/favicon.ico | Bin 4286 -> 14463 bytes types/interfaces.ts | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/composables/auth.ts b/composables/auth.ts index 0ac2e8b..4ec2f32 100644 --- a/composables/auth.ts +++ b/composables/auth.ts @@ -76,6 +76,7 @@ export const useAuth = () => { if (!accessToken.value) return; console.log("fetchuser access token:", accessToken.value); const res = await fetchWithApi("/me") as UserResponse; + res.avatar = res.avatar ?? "https://gorb.b-cdn.net/avatar/default-pfp.png"; user.value = res; return user.value; } diff --git a/public/favicon.ico b/public/favicon.ico index 18993ad91cfd43e03b074dd0b5cc3f37ab38e49c..b3d28fa5964b5ad48b8b2fd2fce843074b44cee3 100644 GIT binary patch literal 14463 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 literal 4286 zcmeHLOKuuL5PjK%MHWVi6lD zOGiREbCw`xmFozJ^aNatJY>w+g ze6a2@u~m#^BZm@8wco9#Crlli0uLb^3E$t2-WIc^#(?t)*@`UpuofJ(Uyh@F>b3Ph z$D^m8Xq~pTkGJ4Q`Q2)te3mgkWYZ^Ijq|hkiP^9`De={bQQ%heZC$QU2UpP(-tbl8 zPWD2abEew;oat@w`uP3J^YpsgT%~jT(Dk%oU}sa$7|n6hBjDj`+I;RX(>)%lm_7N{+B7Mu%H?422lE%MBJH!!YTN2oT7xr>>N-8OF$C&qU^ z>vLsa{$0X%q1fjOe3P1mCv#lN{xQ4_*HCSAZjTb1`}mlc+9rl8$B3OP%VT@mch_~G z7Y+4b{r>9e=M+7vSI;BgB?ryZDY4m>&wcHSn81VH1N~`0gvwH{ z8dv#hG|OK`>1;j7tM#B)Z7zDN?{6=dUal}$e Date: Sun, 1 Jun 2025 19:24:50 +0200 Subject: [PATCH 5/5] feat: profile page, EXCEPT FUCKING AVATARS AAAAAHHH --- components/Userpopup.vue | 48 ++++---- components/buttons/Button.vue | 6 +- components/buttons/ButtonScary.vue | 6 +- components/settings/user_settings/Account.vue | 115 +++++++++++++++++- composables/auth.ts | 1 - pages/settings.vue | 9 +- types/interfaces.ts | 3 +- 7 files changed, 148 insertions(+), 40 deletions(-) diff --git a/components/Userpopup.vue b/components/Userpopup.vue index 1e90026..7381637 100644 --- a/components/Userpopup.vue +++ b/components/Userpopup.vue @@ -3,8 +3,12 @@ profile avatar
-

{{ props.user.display_name || "display_name" }}

-

{{ props.user.username || "username" }}

+

+ {{ props.user.display_name || "display_name" }} +

+

+ {{ props.user.username || "username" }} - {{ props.user.pronouns || "un/defined" }} +

About me
@@ -18,15 +22,15 @@ import type { UserResponse } from '~/types/interfaces'; const { fetchMembers } = useApi(); const props = defineProps<{ - user: UserResponse, + user: UserResponse | any, // actually UserResponse | null but TS is yelling at me again }>(); \ No newline at end of file diff --git a/components/buttons/Button.vue b/components/buttons/Button.vue index 20783c0..2544fbb 100644 --- a/components/buttons/Button.vue +++ b/components/buttons/Button.vue @@ -28,11 +28,11 @@ const props = defineProps<{ background-color: #b35719; color: #ffffff; - padding: 8px 16px; - font-size: 16px; + padding: 8px 18px; + font-size: 18px; transition: background-color 0.2s; - border-radius: 16px; + border-radius: 12px; text-decoration: none; display: inline-block; } diff --git a/components/buttons/ButtonScary.vue b/components/buttons/ButtonScary.vue index 71c0bc7..1fa2c85 100644 --- a/components/buttons/ButtonScary.vue +++ b/components/buttons/ButtonScary.vue @@ -28,11 +28,11 @@ const props = defineProps<{ background-color: #f02f2f; color: #ffffff; - padding: 8px 16px; - font-size: 16px; + padding: 8px 18px; + font-size: 18px; transition: background-color 0.2s; - border-radius: 16px; + border-radius: 12px; text-decoration: none; display: inline-block; } diff --git a/components/settings/user_settings/Account.vue b/components/settings/user_settings/Account.vue index 9b7d044..74d120d 100644 --- a/components/settings/user_settings/Account.vue +++ b/components/settings/user_settings/Account.vue @@ -2,10 +2,31 @@

My Account

-
- +
+
+

AVATAR

+ + +

DISPLAY NAME

+ +

USERNAME

+ +

PRONOUNS

+ +

ABOUT ME

+

{{ user?.about_me || "TBD" }}

+ + +
+
+ +
+
+
+
+

Password (and eventually authenticator)

@@ -19,10 +40,81 @@ import Button from '~/components/buttons/Button.vue'; import ButtonScary from '~/components/buttons/ButtonScary.vue'; -const { user, fetchUser } = useAuth(); +const { fetchUser } = useAuth(); const user_me = await fetchUser() +let user_reference = Object.assign({}, user_me) +const user = user_me! +let new_pfp_file: any = null + +const saveChanges = async () => { + try { + const formData = new FormData() + + if (new_pfp_file) { + formData.append('avatar', new_pfp_file, new_pfp_file.name) + new_pfp_file = null + } + + // oh lord praise deep seek v3 + const jsonBlob = new Blob( + [JSON.stringify({ + display_name: user.display_name, + username: user.username, + pronouns: user.pronouns, + })], + { type: 'application/json' } + ); + formData.append('json', jsonBlob, 'data.json'); + + await fetchWithApi("/me", { + method: "PATCH", + body: formData + }) + + user_reference = Object.assign({}, user_me) + alert("success!!") + } catch (error: any) { + if (error?.response?.status !== 200) { + const errorData = await error?.response?.json() + + alert(`error ${error?.response?.status} met whilst trying to update profile info\n${errorData}`) + } + } +}; + + +const removeAvatar = async () => { + alert("TBD") + // await fetchWithApi(`/auth/reset-password`); +} + +const changeAvatar = async () => { + try { + let input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + + input.onchange = async(e) => { + const file = e.target.files?.[0]; + if (!file) return; + + new_pfp_file = file; + + const reader = new FileReader(); + reader.onload = (e) => { + user.avatar = e?.target?.result; + }; + reader.readAsDataURL(file); + } + + input.oncancel = () => alert("cancelled upload!"); + input.click(); + } catch (err) { + console.error('User canceled or error:', err); + } +} const resetPassword = async () => { alert("TBD") @@ -35,11 +127,24 @@ const deleteAccount = async () => { \ No newline at end of file diff --git a/composables/auth.ts b/composables/auth.ts index 4ec2f32..0ac2e8b 100644 --- a/composables/auth.ts +++ b/composables/auth.ts @@ -76,7 +76,6 @@ export const useAuth = () => { if (!accessToken.value) return; console.log("fetchuser access token:", accessToken.value); const res = await fetchWithApi("/me") as UserResponse; - res.avatar = res.avatar ?? "https://gorb.b-cdn.net/avatar/default-pfp.png"; user.value = res; return user.value; } diff --git a/pages/settings.vue b/pages/settings.vue index 050c1f8..fb66aa2 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -115,8 +115,8 @@ const selectCategory = (_category: Category, page: Page) => { } #sidebar { - min-width: 200px; - max-width: 200px; + min-width: 250px; + max-width: 250px; background-color: #2f3136; color: white; padding: 10px; @@ -127,7 +127,7 @@ const selectCategory = (_category: Category, page: Page) => { } #sidebar h2 { - font-size: 1.5em; + font-size: 2em; padding: 0 8px; } @@ -140,6 +140,7 @@ const selectCategory = (_category: Category, page: Page) => { #sidebar li { border-radius: 8px; padding: 8px; + font-size: 1.3em; margin: 2px 0; cursor: pointer; transition: background-color 0.3s; @@ -155,7 +156,7 @@ const selectCategory = (_category: Category, page: Page) => { #sub_page { flex-grow: 1; - max-width: 600px; + max-width: 800px; margin-left: 1.5rem; margin-right: auto; diff --git a/types/interfaces.ts b/types/interfaces.ts index a0d7d39..04da39a 100644 --- a/types/interfaces.ts +++ b/types/interfaces.ts @@ -57,8 +57,9 @@ export interface UserResponse { uuid: string, username: string, display_name: string | null, - avatar: string, + avatar: string | null, pronouns: string | null, + about_me: string | null, email?: string, email_verified?: boolean }