handmade-revolt/src/pages/channels/messaging/MessageRenderer.tsx

249 lines
7.2 KiB
TypeScript

/* eslint-disable react-hooks/rules-of-hooks */
import { X } from "@styled-icons/boxicons-regular";
import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite";
import { API } from "revolt.js";
import { Message as MessageI } from "revolt.js";
import { Nullable } from "revolt.js";
import styled from "styled-components/macro";
import { decodeTime } from "ulid";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
import { ChannelRenderer } from "../../../lib/renderer/Singleton";
import { useApplicationState } from "../../../mobx/State";
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
import { useClient } from "../../../context/revoltjs/RevoltClient";
import Message from "../../../components/common/messaging/Message";
import { SystemMessage } from "../../../components/common/messaging/SystemMessage";
import DateDivider from "../../../components/ui/DateDivider";
import Preloader from "../../../components/ui/Preloader";
import { Children } from "../../../types/Preact";
import ConversationStart from "./ConversationStart";
import MessageEditor from "./MessageEditor";
interface Props {
last_id?: string;
highlight?: string;
renderer: ChannelRenderer;
}
const BlockedMessage = styled.div`
font-size: 0.8em;
margin-top: 6px;
padding: 4px 64px;
color: var(--tertiary-foreground);
&:hover {
background: var(--hover);
}
`;
export default observer(({ last_id, renderer, highlight }: Props) => {
const client = useClient();
const userId = client.user!._id;
const queue = useApplicationState().queue;
const [editing, setEditing] = useState<string | undefined>(undefined);
const stopEditing = () => {
setEditing(undefined);
internalEmit("TextArea", "focus", "message");
};
useEffect(() => {
function editLast() {
if (renderer.state !== "RENDER") return;
for (let i = renderer.messages.length - 1; i >= 0; i--) {
if (renderer.messages[i].author_id === userId) {
setEditing(renderer.messages[i]._id);
internalEmit("MessageArea", "jump_to_bottom");
return;
}
}
}
const subs = [
internalSubscribe("MessageRenderer", "edit_last", editLast),
internalSubscribe(
"MessageRenderer",
"edit_message",
setEditing as (...args: unknown[]) => void,
),
];
return () => subs.forEach((unsub) => unsub());
}, [renderer.messages, renderer.state, userId]);
const render: Children[] = [];
let previous: MessageI | undefined;
if (renderer.atTop) {
render.push(<ConversationStart channel={renderer.channel} />);
} else {
render.push(
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>,
);
}
let head = true;
let divided = false;
function compare(
current: string,
curAuthor: string,
currentMasq: Nullable<API.Masquerade>,
previous: string,
prevAuthor: string,
previousMasq: Nullable<API.Masquerade>,
) {
head = false;
const atime = decodeTime(current),
adate = new Date(atime),
btime = decodeTime(previous),
bdate = new Date(btime);
let unread = false;
if (!divided && last_id && previous >= last_id) {
unread = true;
divided = true;
}
let date;
if (
adate.getFullYear() !== bdate.getFullYear() ||
adate.getMonth() !== bdate.getMonth() ||
adate.getDate() !== bdate.getDate()
) {
date = adate;
}
if (unread || date) {
render.push(<DateDivider date={date} unread={unread} />);
head = true;
}
head =
head ||
curAuthor !== prevAuthor ||
Math.abs(btime - atime) >= 420000 ||
!isEqual(currentMasq, previousMasq);
}
let blocked = 0;
function pushBlocked() {
render.push(
<BlockedMessage>
<X size={16} />{" "}
<Text
id="app.main.channel.misc.blocked_messages"
fields={{ count: blocked }}
/>
</BlockedMessage>,
);
blocked = 0;
}
for (const message of renderer.messages) {
if (previous) {
compare(
message._id,
message.author_id,
message.masquerade,
previous._id,
previous.author_id,
previous.masquerade,
);
}
if (message.author_id === "00000000000000000000000000") {
render.push(
<SystemMessage
key={message._id}
message={message}
attachContext
highlight={highlight === message._id}
/>,
);
} else if (message.author?.relationship === "Blocked") {
blocked++;
} else {
if (blocked > 0) pushBlocked();
render.push(
<Message
message={message}
key={message._id}
head={head}
content={
editing === message._id ? (
<MessageEditor
message={message}
finish={stopEditing}
/>
) : undefined
}
attachContext
highlight={highlight === message._id}
/>,
);
}
previous = message;
}
if (blocked > 0) pushBlocked();
const nonces = renderer.messages.map((x) => x.nonce);
if (renderer.atBottom) {
for (const msg of queue.get(renderer.channel._id)) {
if (nonces.includes(msg.id)) continue;
if (previous) {
compare(
msg.id,
userId!,
null,
previous._id,
previous.author_id,
previous.masquerade,
);
previous = {
_id: msg.id,
author_id: userId!,
} as MessageI;
}
render.push(
<Message
message={
new MessageI(client, {
...msg.data,
replies: msg.data.replies.map((x) => x.id),
})
}
key={msg.id}
queued={msg}
head={head}
attachContext
/>,
);
}
} else {
render.push(
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>,
);
}
return <>{render}</>;
});