diff --git a/classes/Message.ts b/classes/Message.ts index de0d12b..a782d6a 100644 --- a/classes/Message.ts +++ b/classes/Message.ts @@ -12,8 +12,4 @@ export default class Message { this.userUuid = user_uuid; this.message = message; } - - getTimestamp() { - return uuidToTimestamp(this.uuid); - } } \ No newline at end of file diff --git a/components/Avatar.vue b/components/Avatar.vue index 06d36ad..dcb3b7e 100644 --- a/components/Avatar.vue +++ b/components/Avatar.vue @@ -6,7 +6,7 @@ @@ -14,25 +14,14 @@ import { NuxtImg } from '#components'; import type { GuildMemberResponse, UserResponse } from '~/types/interfaces'; -const { getDisplayName } = useProfile() +const { getDisplayName, getAvatarUrl, getUserUuid } = useProfile() const props = defineProps<{ profile: UserResponse | GuildMemberResponse, }>(); const displayName = getDisplayName(props.profile) -let user: UserResponse -let displayAvatar: string | null - -if ("username" in props.profile) { - // assume it's a UserResponse - displayAvatar = props.profile.avatar - user = props.profile -} else { - // assume it's a GuildMemberResponse - displayAvatar = props.profile.user.avatar - user = props.profile.user -} +const displayAvatar = getAvatarUrl(props.profile) diff --git a/components/DefaultIcon.vue b/components/DefaultIcon.vue index 6fc6891..c374ab9 100644 --- a/components/DefaultIcon.vue +++ b/components/DefaultIcon.vue @@ -17,6 +17,7 @@ const props = defineProps<{ }>(); let previewName = ""; +// include the entire name if it's 3 chars or less, use the first char of the first 3 words otherwise if (props.name.length > 3) { let guildName: string[] = props.name.split(' ') for (let i = 0; i < 3; i ++) { diff --git a/components/Guild/ChannelNavbar.vue b/components/Guild/ChannelNavbar.vue new file mode 100644 index 0000000..c490d73 --- /dev/null +++ b/components/Guild/ChannelNavbar.vue @@ -0,0 +1,85 @@ + + + + + \ No newline at end of file diff --git a/components/Guild/GuildSidebar.vue b/components/Guild/GuildSidebar.vue new file mode 100644 index 0000000..99487b7 --- /dev/null +++ b/components/Guild/GuildSidebar.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/components/Guild/MemberEntry.vue b/components/Guild/MemberEntry.vue index 0c7afa0..f0a8aa5 100644 --- a/components/Guild/MemberEntry.vue +++ b/components/Guild/MemberEntry.vue @@ -37,7 +37,13 @@ function hideModalPopup() { diff --git a/components/Guild/MemberList.vue b/components/Guild/MemberList.vue new file mode 100644 index 0000000..323f53e --- /dev/null +++ b/components/Guild/MemberList.vue @@ -0,0 +1,47 @@ + + + + + \ No newline at end of file diff --git a/components/Message.vue b/components/Message.vue index 74b0bca..6f6c0ba 100644 --- a/components/Message.vue +++ b/components/Message.vue @@ -1,24 +1,22 @@ @@ -71,35 +69,39 @@ import MessageReply from './UserInterface/MessageReply.vue'; import type { ContextMenuInterface, ContextMenuItem } from '~/types/interfaces'; const { getDisplayName } = useProfile() +const { getUser } = useAuth() const route = useRoute(); const props = defineProps(); -const contextMenu = useState("contextMenu"); +const me = await getUser() + +const contextMenu = useState("contextMenu", () => ({ show: false, pointerX: 0, pointerY: 0, items: [] })); const messageElement = ref(); const dateHidden = ref(true); -const date = new Date(props.timestamp); +const date = uuidToDate(props.message.uuid); + const currentDate: Date = new Date() console.log("[MSG] message to render:", props.message); -console.log("author:", props.author); +console.log("author:", props.message.member); console.log("[MSG] reply message:", props.replyMessage); const linkRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g; const linkMatches = props.message.message.matchAll(linkRegex).map(link => link[0]); -const mediaLinks: string[] = []; +const mediaLinks = ref([]); console.log("link matches:", linkMatches); -const hasEmbed = ref(false); +const hideText = ref(false); const sanitized = ref(); onMounted(async () => { - const parsed = await parse(props.text, { gfm: true }); + const parsed = await parse(props.message.message, { gfm: true }); sanitized.value = DOMPurify.sanitize(parsed, { ALLOWED_TAGS: [ "strong", "em", "br", "blockquote", @@ -123,23 +125,35 @@ onMounted(async () => { console.log("added listeners"); } + const links: string[] = []; for (const link of linkMatches) { - console.log("link:", link); - try { - const res = await $fetch.raw(link); - if (res.ok && res.headers.get("content-type")?.match(/^image\/(apng|gif|jpeg|png|webp)$/)) { - console.log("link is image"); - mediaLinks.push(link); + console.log("link:", link); + try { + const res = await $fetch.raw(link); + if (res.ok && res.headers.get("content-type")?.match(/^image\/(apng|gif|jpeg|png|webp)$/)) { + console.log("link is image"); + links.push(link); + } + } catch (error) { + console.error(error); } - if (mediaLinks.length) { - hasEmbed.value = true - }; - } catch (error) { - console.error(error); - } -} -console.log("media links:", mediaLinks); + mediaLinks.value = [...links]; + } + + if (mediaLinks.value.length) { + const nonLinks = props.message.message.split(linkRegex); + let invalidContent = false; + for (const nonLink of nonLinks) { + if (nonLink != "" && nonLink != "\n" && nonLink != "
") { + invalidContent = true; + break; + } + } + hideText.value = !invalidContent; + }; + + console.log("media links:", mediaLinks); }); //function toggleTooltip(e: Event) { @@ -150,13 +164,13 @@ const messageMenuItems: ContextMenuItem[] = [ { name: "Reply", icon: "lucide:reply", type: "normal", callback: () => { if (messageElement.value) replyToMessage(messageElement.value, props) } } ] -console.log("me:", props.me); -if (props.author.user.uuid == props.me.uuid) { +console.log("me:", me); +if (props.message.member.user.uuid == me!.uuid) { // Inserts "edit" option at index 1 (below the "reply" option) messageMenuItems.splice(Math.min(1, messageMenuItems.length), 0, { name: "Edit (WIP)", icon: "lucide:square-pen", type: "normal", callback: () => { /* if (messageElement.value) editMessage(messageElement.value, props) */ } }); } -if (props.author.user.uuid == props.me.uuid /* || check message delete permission*/) { +if (props.message.member.user.uuid == me!.uuid /* || check message delete permission*/) { // Inserts "edit" option at index 2 (below the "edit" option) messageMenuItems.splice(Math.min(2, messageMenuItems.length), 0, { name: "Delete (WIP)", icon: "lucide:trash", type: "danger", callback: () => {} }); } @@ -178,14 +192,20 @@ function getDayDifference(date1: Date, date2: Date) { \ No newline at end of file diff --git a/components/Modal/Base.vue b/components/Modal/Base.vue index a5d8ebd..3fd7079 100644 --- a/components/Modal/Base.vue +++ b/components/Modal/Base.vue @@ -1,18 +1,11 @@ - \ No newline at end of file diff --git a/components/Modal/Invite.vue b/components/Modal/Invite.vue index 232114c..de842a2 100644 --- a/components/Modal/Invite.vue +++ b/components/Modal/Invite.vue @@ -40,7 +40,8 @@ function copyInvite(type: "link" | "code") { if (!invite.value) return; if (type == "link") { - const inviteUrl = URL.parse(`invite/${invite.value}`, `${window.location.protocol}//${window.location.host}`); + const runtimeConfig = useRuntimeConfig(); + const inviteUrl = URL.parse(`invite/${invite.value}`, `${window.location.protocol}//${window.location.host}${runtimeConfig.app.baseURL}`); if (inviteUrl) { navigator.clipboard.writeText(inviteUrl.href); } diff --git a/components/Modal/ProfilePopup.vue b/components/Modal/ProfilePopup.vue index b99a9ea..c7407c3 100644 --- a/components/Modal/ProfilePopup.vue +++ b/components/Modal/ProfilePopup.vue @@ -27,15 +27,17 @@ -
-
-
-
- {{ " " + aboutMe }} +
+
+
+
+
+ {{ " " + aboutMe }} +
+
-
@@ -44,27 +45,16 @@ - -

TIME FORMAT

-
- -
\ No newline at end of file diff --git a/composables/profile.ts b/composables/profile.ts index f3515e6..25b18c2 100644 --- a/composables/profile.ts +++ b/composables/profile.ts @@ -3,7 +3,7 @@ import type { GuildMemberResponse, UserResponse } from "~/types/interfaces" const { fetchFriends } = useApi(); export const useProfile = () => { - function getAboutMe (profile: UserResponse | GuildMemberResponse): string | null { + function getAboutMe(profile: UserResponse | GuildMemberResponse): string | null { if ("username" in profile) { return profile.about } else { @@ -11,7 +11,7 @@ export const useProfile = () => { } } - function getDisplayName (profile: UserResponse | GuildMemberResponse): string { + function getDisplayName(profile: UserResponse | GuildMemberResponse): string { if ("username" in profile) { // assume it's a UserResponse if (profile.display_name) return profile.display_name @@ -24,7 +24,15 @@ export const useProfile = () => { } } - async function getFriendsSince (profile: UserResponse | GuildMemberResponse): Promise { + function getAvatarUrl(profile: UserResponse | GuildMemberResponse): string | null { + if ("username" in profile) { + return profile.avatar + } else { + return profile.user.avatar + } + } + + async function getFriendsSince(profile: UserResponse | GuildMemberResponse): Promise { let user_uuid: string; if ("username" in profile) { @@ -42,15 +50,15 @@ export const useProfile = () => { return null } - function getGuildJoinDate (profile: UserResponse | GuildMemberResponse): Date | null { + function getGuildJoinDate(profile: UserResponse | GuildMemberResponse): Date | undefined { if ("username" in profile) { - return null + return undefined } else { return uuidToDate(profile.uuid) } } - function getPronouns (profile: UserResponse | GuildMemberResponse): string | null { + function getPronouns(profile: UserResponse | GuildMemberResponse): string | null { if ("username" in profile) { return profile.pronouns } else { @@ -58,7 +66,7 @@ export const useProfile = () => { } } - function getRegistrationDate (profile: UserResponse | GuildMemberResponse): Date | null { + function getRegistrationDate(profile: UserResponse | GuildMemberResponse): Date { if ("username" in profile) { return uuidToDate(profile.uuid) } else { @@ -66,7 +74,7 @@ export const useProfile = () => { } } - function getUsername (profile: UserResponse | GuildMemberResponse): string { + function getUsername(profile: UserResponse | GuildMemberResponse): string { if ("username" in profile) { return profile.username } else { @@ -74,22 +82,32 @@ export const useProfile = () => { } } - function getUuid (profile: UserResponse | GuildMemberResponse): string | null { + function getUserUuid(profile: UserResponse | GuildMemberResponse): string { if ("username" in profile) { return profile.uuid } else { return profile.user.uuid } } + + function getUser(profile: UserResponse | GuildMemberResponse): UserResponse { + if ("username" in profile) { + return profile + } else { + return profile.user + } + } return { getAboutMe, getDisplayName, + getAvatarUrl, getFriendsSince, getGuildJoinDate, getRegistrationDate, getPronouns, getUsername, - getUuid + getUserUuid, + getUser } } diff --git a/layouts/client.vue b/layouts/client.vue index 800a097..eb1c524 100644 --- a/layouts/client.vue +++ b/layouts/client.vue @@ -1,59 +1,17 @@ diff --git a/pages/index.vue b/pages/index.vue index cb5f57e..30e0793 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,18 +1,4 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/pages/me/[userId].vue b/pages/me/[userId].vue index 209ffab..cf3eb4f 100644 --- a/pages/me/[userId].vue +++ b/pages/me/[userId].vue @@ -8,6 +8,7 @@ \ No newline at end of file diff --git a/public/themes/layout/gorb.css b/public/themes/layout/gorb.css index 7b8e88b..7fb5049 100644 --- a/public/themes/layout/gorb.css +++ b/public/themes/layout/gorb.css @@ -8,8 +8,14 @@ complementaryColor = white --sidebar-icon-width: 2.5em; --sidebar-icon-gap: .25em; --sidebar-margin: .5em; + + --navbar-height: 5dvh; + --navbar-icon-size: 3dvh; + --navbar-gap: calc(3dvh * .2); + --minor-radius: .35em; --standard-radius: .5em; + --embed-radius: .3em; --button-radius: .6em; --guild-icon-radius: 15%; --pfp-radius: 50%; diff --git a/public/themes/style/ash.css b/public/themes/style/ash.css index 495fa15..1437777 100644 --- a/public/themes/style/ash.css +++ b/public/themes/style/ash.css @@ -10,15 +10,16 @@ complementaryColor = white --reply-text-color: #969696; --danger-text-color: #ff0000; - --chat-background-color: #2f2e2d; - --chat-highlighted-background-color: #3f3b38; + --chat-background-color: #383432; + --chat-highlighted-background-color: #4c4a48; --chat-important-background-color: #ffcf5f38; --chat-important-highlighted-background-color: #ffa86f4f; --chat-featured-message-color: #4f3f2f60; --popup-background-color: #2f2828; --popup-highlighted-background-color: #382f2f; + --modal-background-color: #3a352f; - --sidebar-background-color: #3e3a37; + --sidebar-background-color: #322f2d; --sidebar-highlighted-background-color: #46423b; --topbar-background-color: #3a3733; --chatbox-background-color: #3a3733; diff --git a/public/themes/style/dark.css b/public/themes/style/dark.css index d23ed76..53c0659 100644 --- a/public/themes/style/dark.css +++ b/public/themes/style/dark.css @@ -10,17 +10,18 @@ complementaryColor = white --reply-text-color: #969696; --danger-text-color: #ff0000; - --chat-background-color: #1f1e1d; - --chat-highlighted-background-color: #2f2b28; + --chat-background-color: #282624; + --chat-highlighted-background-color: #383430; --chat-important-background-color: #ffc44f2f; --chat-important-highlighted-background-color: #ffa45f4a; --chat-featured-message-color: #4f2f1f58; --popup-background-color: #2f1f1f; --popup-highlighted-background-color: #3f2f2f; + --modal-background-color: #28241f; - --sidebar-background-color: #2e2a27; - --sidebar-highlighted-background-color: #36322b; - --topbar-background-color: #2a2723; + --sidebar-background-color: #1f1e1d; + --sidebar-highlighted-background-color: #2f2b28; + --topbar-background-color: #1f1e1e; --chatbox-background-color: #1a1713; --padding-color: #484848; diff --git a/public/themes/style/description.css b/public/themes/style/description.css index 972821c..45829e0 100644 --- a/public/themes/style/description.css +++ b/public/themes/style/description.css @@ -18,6 +18,7 @@ complementaryColor = black --chat-featured-message-color: #4f2f1f58; --popup-background-color: #2f1f1f; --popup-highlighted-background-color: #3f2f2f; + --modal-background-color: #181f1f; --sidebar-background-color: #80808000; --sidebar-highlighted-background-color: #ffffff20; diff --git a/public/themes/style/light.css b/public/themes/style/light.css index 78bf2f9..8d7a0c8 100644 --- a/public/themes/style/light.css +++ b/public/themes/style/light.css @@ -10,13 +10,14 @@ complementaryColor = black --reply-text-color: #969696; --danger-text-color: #ff0000; - --chat-background-color: #f0ebe8; - --chat-highlighted-background-color: #e8e4e0; + --chat-background-color: #f0edeb; + --chat-highlighted-background-color: #aba8a4; --chat-important-background-color: #df5f0b26; --chat-important-hightlighted-background-color: #df5f0b3d; --chat-featured-message-color: #e8ac841f; - --popup-background-color: #e8e4e0; - --popup-highlighted-background-color: #dfdbd6; + --popup-background-color: #b8b4b0; + --popup-highlighted-background-color: #a6a4a2; + --modal-background-color: #e8e4e0; --sidebar-background-color: #dbd8d4; --sidebar-highlighted-background-color: #d4d0ca; diff --git a/public/themes/style/rainbow-capitalism.css b/public/themes/style/rainbow-capitalism.css index 8ca3ed7..df01d39 100644 --- a/public/themes/style/rainbow-capitalism.css +++ b/public/themes/style/rainbow-capitalism.css @@ -17,6 +17,7 @@ complementaryColor = white --chat-featured-message-color: #4f8f4f80; --popup-background-color: #80808080; --popup-highlighted-background-color: #9f9f9f9f; + --modal-background-color: #7fa87fff; --sidebar-background-color: #80808000; --sidebar-highlighted-background-color: #ffffff20; diff --git a/types/interfaces.ts b/types/interfaces.ts index d175d76..bb6489b 100644 --- a/types/interfaces.ts +++ b/types/interfaces.ts @@ -121,3 +121,22 @@ export interface ContextMenuInterface { pointerY: number, items: ContextMenuItem[] } + +export interface NavbarItem { + title: string, + icon: string, + hasPing?: boolean, // whether to draw a "ping" icon or not + callback: (...args: any[]) => any; +} + +export interface INavbar { + guild: GuildResponse + channel: ChannelResponse +} + +export interface NavbarOptions { + guild?: GuildResponse + channel?: ChannelResponse + isDirectMessages?: boolean +} + diff --git a/types/props.ts b/types/props.ts index f25ae8d..5796d83 100644 --- a/types/props.ts +++ b/types/props.ts @@ -1,21 +1,9 @@ -import type { GuildMemberResponse, MessageResponse, UserResponse } from "./interfaces"; +import type { MessageResponse, UserResponse } from "./interfaces"; export interface MessageProps { - class?: string, - img?: string | null, - author: GuildMemberResponse - text: string, - timestamp: number, - format: "12" | "24", - type: "normal" | "grouped", - marginBottom: boolean, - authorColor: string, - last: boolean, - messageId: string, - replyingTo?: boolean, - editing?: boolean, - me: UserResponse message: MessageResponse, - replyMessage?: MessageResponse + replyMessage?: MessageResponse, + type: "normal" | "grouped", + editing?: boolean, isMentioned?: boolean, } \ No newline at end of file diff --git a/types/settings.ts b/types/settings.ts index 8d24904..0e80dca 100644 --- a/types/settings.ts +++ b/types/settings.ts @@ -4,7 +4,4 @@ export interface ClientSettings { selectedThemeLayout?: string // URL } -export interface TimeFormat { - index: number, - format: "auto" | "12" | "24" -} \ No newline at end of file +export type TimeFormat = "Auto" | "4:18 PM" | "16:18" \ No newline at end of file diff --git a/utils/getPreferredTimeFormat.ts b/utils/getPreferredTimeFormat.ts index 86b9635..cbdd370 100644 --- a/utils/getPreferredTimeFormat.ts +++ b/utils/getPreferredTimeFormat.ts @@ -1,9 +1,9 @@ export default (): "12" | "24" => { - const format = settingsLoad().timeFormat?.format ?? "auto" + const format = settingsLoad().timeFormat || "Auto" - if (format == "12") { + if (format == "4:18 PM") { return "12" - } else if (format == "24") { + } else if (format == "16:18") { return "24" } diff --git a/utils/loadPreferredThemes.ts b/utils/loadPreferredThemes.ts index 00a4d1b..85b9c38 100644 --- a/utils/loadPreferredThemes.ts +++ b/utils/loadPreferredThemes.ts @@ -2,20 +2,17 @@ let styleLinkElement: HTMLLinkElement | null; let layoutLinkElement: HTMLLinkElement | null; -export default () => { +export default () => { const runtimeConfig = useRuntimeConfig() const baseURL = runtimeConfig.app.baseURL; - - let currentStyle = settingsLoad().selectedThemeStyle ?? undefined - let currentLayout = settingsLoad().selectedThemeLayout ?? `${baseURL}themes/layout/gorb.css` - if (!currentStyle) { - if (prefersLight()) { - currentStyle = `${baseURL}themes/style/light.css` - } else { - currentStyle = `${baseURL}themes/style/dark.css` - } - } + let currentStyle = settingsLoad().selectedThemeStyle || ( + prefersLight() + ? `${baseURL}themes/style/light.css` + : `${baseURL}themes/style/dark.css` + ); + + let currentLayout = settingsLoad().selectedThemeLayout || `${baseURL}themes/layout/gorb.css` if (styleLinkElement) { styleLinkElement.href = currentStyle; diff --git a/utils/replyToMessage.ts b/utils/replyToMessage.ts index 83f4eda..1ff4df7 100644 --- a/utils/replyToMessage.ts +++ b/utils/replyToMessage.ts @@ -9,7 +9,7 @@ export default (element: HTMLDivElement, props: MessageProps) => { const messageBox = document.getElementById("message-box") as HTMLDivElement; if (messageBox) { const div = document.createElement("div"); - const messageReply = h(MessageReply, { author: getDisplayName(props.author), text: props.text || "", id: props.message.uuid, replyId: props.replyMessage?.uuid || element.dataset.messageId!, maxWidth: "full" }); + const messageReply = h(MessageReply, { author: getDisplayName(props.message.member), text: props.message.message || "", id: props.message.uuid, replyId: props.replyMessage?.uuid || element.dataset.messageId!, maxWidth: "full" }); messageBox.prepend(div); render(messageReply, div); const message = document.querySelector(`.message[data-message-id='${props.message.uuid}']`);