not so initial commit
This commit is contained in:
150
src/components/Message.tsx
Normal file
150
src/components/Message.tsx
Normal 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 it’s 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user