import { RefObject, useEffect, useRef, useSyncExternalStore, type ReactElement } from "react"; import { user, message } from "@localtypes"; import { subscribeUsers, getUsers, subscribeMessages, getMessages, self } from "@ws"; function formatUnixTimestamp(ts: number) { const date = new Date(ts); const now = new Date(); const isToday = date.getDate() === now.getDate() && date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear(); const HH = String(date.getHours()).padStart(2, '0'); const min = String(date.getMinutes()).padStart(2, '0'); if (isToday) { return `Today at ${HH}:${min}`; } const dd = String(date.getDate()).padStart(2, '0'); const mm = String(date.getMonth() + 1).padStart(2, '0'); const yy = String(date.getFullYear()) return `${dd}/${mm}/${yy} ${HH}:${min}`; } export function RenderMessages(): ReactElement { let currentAuthor: user | null = null; const messages: (message | null)[] = useSyncExternalStore( subscribeMessages, getMessages ); console.log(messages); const bottomRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "instant" }); }, [messages.length]); return (
{messages.map((message, index) => { if (message) { const nextMessage = messages[index + 1]; const nextAuthor = nextMessage ? nextMessage.author : null; const isLastMessageFromAuthor = nextAuthor?.id !== message.author.id; const inline = currentAuthor?.id === message.author.id; if (!inline) { currentAuthor = message.author; } return ( `) || message.content.includes("@everyone") || message.content.includes("@here")} /> ); } else return "" })}
); } function Message({author, content, inline = false, bottom = false, timestamp = 0, hasSent, mention}: {author: user, content: string, inline?: boolean, bottom?: boolean, timestamp?: number, hasSent: boolean, mention: boolean}): ReactElement { const users: user[] = useSyncExternalStore( subscribeUsers, getUsers ); function formatContent(content: string): ReactElement[] { const parts = content.split('\n'); // Split the content into parts by newlines return parts.flatMap((part, index) => { const elements: ReactElement[] = []; let lastIndex = 0; // Match all mentions in the part (e.g., <@123>) const mentionRegex = /<@(\d+)>/g; let match; while ((match = mentionRegex.exec(part)) !== null) { const [mentionTag, userId] = match; const textBeforeMention = part.slice(lastIndex, match.index); // Add the text before the mention as plain text if (textBeforeMention) { elements.push({textBeforeMention}); } // Add the Mention component const user = users.find((user) => user.id === parseInt(userId!)); // Use parseInt for ID comparison elements.push( ); lastIndex = mentionRegex.lastIndex; // Update the last index } // Add any remaining text after the last mention (if any) const remainingText = part.slice(lastIndex); if (remainingText) { elements.push({remainingText}); } // Add a
between parts if it’s not the last part if (index < parts.length - 1) { elements.push(
); } return elements; }); } const formattedContent = formatContent(content); return (
{!inline ? : ""} {!inline ?
{self.dev ? `${author.name} (${author.username}, ${author.id})` : author.name ?? author.username} {author.dev ? : ""}
: ""}
{formattedContent}
) } function Badge({text, color = "#a200ff"}: {text: string, color?: string}) { return (
{text}
) } function Timestamp({ts}: {ts: number}): ReactElement { return (
{formatUnixTimestamp(ts)}
) } function Mention({name, username}: { name?: string, username: string }): ReactElement { return (
@{name ?? username}
) }