Friendship (request/accept/decline/remove, self-request and crossing-request handling, DB-level pair uniqueness via a hand-written expression index), ChatRoom/ChatMessage/ChatReadState backed by Nitro's built-in websocket (server/api/v1/community/ws.get.ts) behind a ChatTransport seam (server/internal/community/chatTransport.ts) with a REST send fallback. Mid-milestone product redirect: chat's primary job is coordinating the persistent-world servers (EQEmu/WoW/DAoC), not per-title discussion -- ChatRoomKind gets a first-class `server` case and chatService.ts ships a static KNOWN_SERVERS registry keyed the same way M4's GameServer will be, once that lands (gameServerId reserved for that wiring-up pass). Presence has no new table -- "online" is a live websocket registry (presenceRuntime.ts), "playing X" reads through an isolated query module (presenceService.ts) that degrades to an empty, clearly-labeled result if PlaySession's shape doesn't match what it expects. Pages: /community/friends, /community/chat, plus a friends/servers strip on /community itself. Migration 20260803120000_m3_friends_chat.
39 lines
1.1 KiB
Vue
39 lines
1.1 KiB
Vue
<template>
|
|
<img
|
|
v-if="avatarUrl"
|
|
:src="avatarUrl"
|
|
:style="style"
|
|
class="rounded-full object-cover shrink-0"
|
|
:alt="name"
|
|
/>
|
|
<div
|
|
v-else
|
|
:style="style"
|
|
class="rounded-full bg-zinc-800 flex items-center justify-center text-zinc-500 font-bold font-display shrink-0"
|
|
>
|
|
{{ name.charAt(0).toUpperCase() }}
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
// Small shared avatar block (img, or an initial-letter fallback) -- pulled
|
|
// out because M3's friends/chat UI renders many small avatars in lists,
|
|
// unlike the rest of /community which only ever renders one big one per
|
|
// page. Sized via inline style rather than a Tailwind size-N class so an
|
|
// arbitrary numeric prop doesn't depend on that exact class string
|
|
// appearing literally somewhere for Tailwind's scanner to pick up.
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
avatarUrl?: string | null;
|
|
name: string;
|
|
sizePx?: number;
|
|
}>(),
|
|
{ avatarUrl: null, sizePx: 32 },
|
|
);
|
|
|
|
const style = computed(() => ({
|
|
width: `${props.sizePx}px`,
|
|
height: `${props.sizePx}px`,
|
|
fontSize: `${Math.round(props.sizePx * 0.4)}px`,
|
|
}));
|
|
</script>
|