not so initial commit

This commit is contained in:
2026-01-11 03:50:03 +01:00
commit a2016e2b0e
41 changed files with 54638 additions and 0 deletions

150
src/components/Message.tsx Normal file
View File

@@ -0,0 +1,150 @@
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<HTMLDivElement | null>(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: "instant" });
}, [messages.length]);
return (
<div className="messages">
{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
key={message.id}
author={message.author}
content={message.content}
inline={inline}
bottom={isLastMessageFromAuthor}
timestamp={message.timestamp}
hasSent={message.hasSent}
mention={message.content.includes(`<@${self.id}>`) || message.content.includes("@everyone") || message.content.includes("@here")}
/>
);
} else return ""
})}
<div ref={bottomRef} />
</div>
);
}
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(<span key={`${index}-text-${lastIndex}`}>{textBeforeMention}</span>);
}
// Add the Mention component
const user = users.find((user) => user.id === parseInt(userId!)); // Use parseInt for ID comparison
elements.push(
<Mention
key={`${index}-mention-${userId}`}
name={user?.name}
username={user ? user.username : 'unknown-user'}
/>
);
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(<span key={`${index}-text-${lastIndex}`}>{remainingText}</span>);
}
// Add a <br /> between parts if its not the last part
if (index < parts.length - 1) {
elements.push(<br key={`br-${index}`} />);
}
return elements;
});
}
const formattedContent = formatContent(content);
return (
<div className={`message ${inline ? "inline" : ""} ${bottom ? "bottom" : ""} ${hasSent ? "" : "sending"} ${mention ? "mentionsyou" : ""}`}>
{!inline ? <img className="messagePfp" src={`/pfps/${author.pfp}.png`} width={48} height={48}></img> : ""}
{!inline ? <div className="messageAuthor"><span>{self.dev ? `${author.name} (${author.username}, ${author.id})` : author.name ?? author.username}</span> {author.dev ? <Badge text="DEV" /> : ""} <Timestamp ts={timestamp} /></div> : ""}
<div className="messageContent">{formattedContent}</div>
</div>
)
}
function Badge({text, color = "#a200ff"}: {text: string, color?: string}) {
return (
<div className="badge" style={{["--badge-color" as any]: color ?? "var(--brand-color)"}}>{text}</div>
)
}
function Timestamp({ts}: {ts: number}): ReactElement {
return (
<div className="messageTimestamp">{formatUnixTimestamp(ts)}</div>
)
}
function Mention({name, username}: { name?: string, username: string }): ReactElement {
return (
<div className="mention">@{name ?? username}</div>
)
}